VUE开发--Element-UI--树形表格(六十)

一、安装组件

npm i element-ui -S

二、创建组件

  1. 创建目录treeTable
  2. 创建数组转换文件
'use strict'
import Vue from 'vue'
export default function treeToArray (data, expandAll, parent = null, level = null) {
  let tmp = []
  Array.from(data).forEach(function (record) {
    if (record._expanded === undefined) {
      Vue.set(record, '_expanded', expandAll)
    }
    let _level = 1
    if (level !== undefined && level !== null) {
      _level = level + 1
    }
    Vue.set(record, '_level', _level)
    // 如果有父元素
    if (parent) {
      Vue.set(record, 'parent', parent)
    }
    tmp.push(record)
    if (record.children && record.children.length > 0) {
      const children = treeToArray(record.children, expandAll, record, _level)
      tmp = tmp.concat(children)
    }
  })
  return tmp
}
  1. 创建组件
    在目录中创建组件:index.vue
<template>
  <div>
    <el-table :data="formatData" :row-style="showRow" v-bind="$attrs">
      <el-table-column v-if="columns.length===0" width="150">
        <template slot-scope="scope">
          <span v-for="space in scope.row._level" class="ms-tree-space" :key="space"></span>
          <span
            class="tree-ctrl"
            v-if="iconShow(0,scope.row)"
            @click="toggleExpanded(scope.$index)"
          >
            <i v-if="!scope.row._expanded" class="el-icon-plus"></i>
            <i v-else class="el-icon-minus"></i>
          </span>
          {{scope.$index}}
        </template>
      </el-table-column>
      <el-table-column
        v-else
        v-for="(column, index) in columns"
        :key="column.value"
        :label="column.text"
        :width="column.width"
      >
        <template slot-scope="scope">
          <span
            v-if="index === 0"
            v-for="space in scope.row._level"
            class="ms-tree-space"
            :key="space"
          ></span>
          <span
            class="tree-ctrl"
            v-if="iconShow(index,scope.row)"
            @click="toggleExpanded(scope.$index)"
          >
            <i v-if="!scope.row._expanded" class="el-icon-plus"></i>
            <i v-else class="el-icon-minus"></i>
          </span>

          <el-checkbox-group
            v-if="Array.isArray(scope.row[column.value])"
            v-model="scope.row.selectchecked"
            @change="handleCheckedCitiesChange(scope.$index, scope.row,scope.row[column.option])"
          >
            <el-checkbox
              v-for="(interset) in scope.row[column.value]"
              :label="interset.id"
              :key="interset.id"
            >{{interset.description}}</el-checkbox>
          </el-checkbox-group>

          <el-checkbox
            v-else-if="scope.row.type===1"
            :indeterminate="scope.row.isIndeterminate"
            v-model="scope.row.checkAll"
            @change="handleCheckAllChange(scope.$index, scope.row,scope.row[column.option])"
          >{{scope.row[column.value]}}</el-checkbox>
          <span v-else>{{scope.row[column.value]}}</span>
          <el-checkbox
            v-if="scope.row[column.act]"
            :indeterminate="scope.row.isIndeterminate"
            v-model="scope.row.checkAll"
            @change="handleCheckAllChange1(scope.$index, scope.row,column.option)"
          >{{scope.row[column.act]}}</el-checkbox>
        </template>
      </el-table-column>
      <slot></slot>
    </el-table>
    <footer>
      <el-button @click="getAuth">确定</el-button>
    </footer>
  </div>
</template>

<script>
/**
  Auth: Lei.j1ang
  Created: 2018/1/19-13:59
*/
import treeToArray from "./eval";
export default {
  name: "treeTable",
  props: {
    data: {
      type: [Array, Object],
      required: true
    },
    columns: {
      type: Array,
      default: () => []
    },
    evalFunc: Function,
    evalArgs: Array,
    expandAll: {
      type: Boolean,
      // 默认展开
      default: true
    }
  },
  computed: {
    // 格式化数据源
    formatData: function() {
      let tmp;
      if (!Array.isArray(this.data)) {
        tmp = [this.data];
      } else {
        tmp = this.data;
      }
      const func = this.evalFunc || treeToArray;
      const args = this.evalArgs
        ? Array.concat([tmp, this.expandAll], this.evalArgs)
        : [tmp, this.expandAll];
      return func.apply(null, args);
    }
  },
  created() {
    this.defaultSelcet();
  },
  updated() {
    // 需要在vue的updated周期函数中调用methods里的方法   否则methods里面获取不到页面元素
    this.expandAllClick();
  },
  methods: {
    expandAllClick() {
      // 获取到页面元素  模拟点击可实现让树形表格展开
      var els = document.getElementsByClassName("el-icon-minus"); // 获取点击的箭头元素
      console.log(els);
      for (let i = 0; i < els.length; i++) {
        els[i].click();
      }
    },
    showRow: function(row) {
      const show = row.row.parent
        ? row.row.parent._expanded && row.row.parent._show
        : true;
      row.row._show = show;
      return show
        ? "animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;"
        : "display:none;";
    },
    // 切换下级是否展开
    toggleExpanded: function(trIndex) {
      const record = this.formatData[trIndex];
      record._expanded = !record._expanded;
    },
    // 图标显示
    iconShow(index, record) {
      return index === 0 && record.children && record.children.length > 0;
    },
    handleCheckAllChange(index, row, opt) {
      this.cc();
      if (row.selectchecked.length && row.selectchecked.length !== opt.length) {
        let arr = [];
        opt.forEach(element => {
          arr.push(element.id);
        });
        row.selectchecked = arr;
        row.checkAll = true;
        row.isIndeterminate = false;
      } else if (!row.selectchecked.length) {
        let arr = [];
        opt.forEach(element => {
          arr.push(element.id);
        });
        row.selectchecked = arr;
        row.checkAll = true;
        row.isIndeterminate = false;
      } else {
        row.selectchecked = [];
        row.checkAll = false;
        row.isIndeterminate = false;
      }
    },
    handleCheckedCitiesChange(index, row, opt) {
      row.checkAll = row.selectchecked.length === opt.length;
      row.isIndeterminate =
        row.selectchecked.length > 0 && row.selectchecked.length < opt.length;
      this.cc();
    },
    handleCheckAllChange1(index, row, opt) {
      if (row.children) {
        row.children.forEach(val => {
          let arr = [];
          if (row.checkAll) {
            val[opt].forEach(element => {
              arr.push(element.id);
            });
            val.selectchecked = arr;
            val.checkAll = true;
            val.isIndeterminate = false;
          } else {
            val.selectchecked = [];
            val.checkAll = false;
            val.isIndeterminate = false;
          }
        });
      }
      this.cc();
    },
    defaultSelcet() {
      this.data.forEach(val => {
        if (val.children) {
          val.children.forEach(el => {
            if (
              el.selectchecked.length &&
              el.selectchecked.length !== el[this.columns[0].option].length
            ) {
              el.isIndeterminate = true;
              el.checkAll = false;
            } else if (
              el.selectchecked.length &&
              el.selectchecked.length === el[this.columns[0].option].length
            ) {
              el.isIndeterminate = false;
              el.checkAll = true;
            } else {
              el.isIndeterminate = false;
              el.checkAll = false;
            }
          });
          this.cc();
        }
      });
    },
    cc() {
      this.data.forEach(val => {
        let checkAllArr = [];
        let isIndeterminateArr = [];
        if (val.children) {
          val.children.forEach(el => {
            checkAllArr.push(el.checkAll);
            isIndeterminateArr.push(el.isIndeterminate);
          });
        }
        if (new Set(checkAllArr).size === 1) {
          // && new Set(isIndeterminateArr).size !== 1
          if (checkAllArr[0] && isIndeterminateArr[0] === false) {
            val.isIndeterminate = false;
            val.checkAll = true;
          } else if (checkAllArr[0] && new Set(isIndeterminateArr).size !== 1) {
            val.isIndeterminate = false;
            val.checkAll = true;
          } else if (
            !checkAllArr[0] &&
            new Set(isIndeterminateArr).size !== 1
          ) {
            val.isIndeterminate = true;
            val.checkAll = false;
          } else if (
            !checkAllArr[0] &&
            new Set(isIndeterminateArr).size === 1
          ) {
            if (!isIndeterminateArr[0]) {
              val.isIndeterminate = false;
              val.checkAll = false;
            } else {
              val.isIndeterminate = true;
              val.checkAll = false;
            }
          } else {
            val.isIndeterminate = false;
            val.checkAll = false;
          }
        } else {
          val.isIndeterminate = true;
          val.checkAll = false;
        }
      });
    },
    getAuth() {
      this.$emit("getAuth", this.data);
    }
  }
};
</script>
<style rel="stylesheet/css">
@keyframes treeTableShow {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@-webkit-keyframes treeTableShow {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
.el-table__body {
  text-align: left;
}
</style>

<style lang="scss" rel="stylesheet/scss" scoped>
footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 15px;
}
$color-blue: #2196f3;
$space-width: 18px;
.ms-tree-space {
  position: relative;
  top: 1px;
  display: inline-block;
  font-style: normal;
  font-weight: 400;
  line-height: 1;
  width: $space-width;
  height: 14px;
  &::before {
    content: "";
  }
}
.processContainer {
  width: 100%;
  height: 100%;
}
table td {
  line-height: 26px;
}
.tree-ctrl {
  position: relative;
  cursor: pointer;
  color: $color-blue;
  margin-left: -$space-width;
}
</style>
  1. 使用组件TableTree.vue
<template>
  <div class="app-container">
    <tree-table :data="data" :columns="columns" border @getAuth="getAuth"></tree-table>

  </div>
</template>

<script>
/**
  Explain:根据花裤衩的表格改的,
  isIndeterminate属性是控制多选半选中状态,
  checkAll是控制全选中状态
  selectchecked是放置sonData1选中项
*/
import treeTable from '@/components/treeTable'
export default {
  name: 'treeTableDemo',
  components: { treeTable },
  data () {
    return {
      columns: [
        {
          text: '菜单列表',
          value: 'description',
          width: 200,
          option: 'sonData1'
        },
        {
          text: '功能权限',
          value: 'sonData1',
          option: 'sonData1',
          act: 'act'
        }
      ],
      data: [
        {
          type: 0,
          'checked': false,
          'id': '1',
          'description': '用户管理',
          isIndeterminate: false,
          checkAll: false,
          act: '全选',
          children: [
            {
              type: 1,
              id: 6,
              'description': '用户列表',
              'parentId': '-1',
              'checked': false,
              selectchecked: ['7'],
              checkAll: false,
              isIndeterminate: false,
              'sonData1': [
                {
                  type: 2,
                  'description': '用户新增',
                  'parentId': '6',
                  'checked': false,
                  'id': '7'
                },
                {
                  type: 2,
                  'description': '用户修改',
                  'parentId': '6',
                  'checked': false,
                  'id': '8'
                },
                {
                  type: 2,
                  'description': '用户删除',
                  'parentId': '6',
                  'checked': false,
                  'id': '9'
                }
              ]
            },
            {
              type: 1,
              id: 13,
              'description': '角色列表',
              'parentId': '-1',
              'checked': false,
              selectchecked: ['10', '11', '12'],
              checkAll: false,
              isIndeterminate: false,
              'sonData1': [
                {
                  type: 2,
                  'description': '角色授权',
                  'parentId': '6',
                  'checked': false,
                  'id': '10'
                },
                {
                  type: 2,
                  'description': '角色修改',
                  'parentId': '6',
                  'checked': false,
                  'id': '11'
                },
                {
                  type: 2,
                  'description': '角色删除',
                  'parentId': '6',
                  'checked': false,
                  'id': '12'
                }
              ]
            }
          ]
        },
        {
          type: 0,
          'checked': false,
          'id': '2',
          'description': '设备管理',
          isIndeterminate: false,
          checkAll: false,
          act: '全选',
          children: [
            {
              type: 1,
              id: 6,
              'description': '设备列表',
              'parentId': '-1',
              'checked': false,
              selectchecked: [],
              checkAll: false,
              isIndeterminate: false,
              'sonData1': [
                {
                  type: 2,
                  'description': '设备新增',
                  'parentId': '6',
                  'checked': false,
                  'id': '17'
                },
                {
                  type: 2,
                  'description': '设备修改',
                  'parentId': '6',
                  'checked': false,
                  'id': '18'
                },
                {
                  type: 2,
                  'description': '设备删除',
                  'parentId': '6',
                  'checked': false,
                  'id': '19'
                }
              ]
            }
          ]
        }
      ]
    }
  },
  created () {
  },
  methods: {
    getAuth (data) {
      let opt = []
      data.forEach(val => {
        opt.push(val.id)
        if (val.children) {
          val.children.forEach(el => {
            console.log(val.id)
            if (el.selectchecked.length) {
              opt.push(el.id)
              opt.push(el.selectchecked)
            }
          })
        }
      })
      console.log(data)
      opt = opt.join().split(',').filter(n => { return n })
      console.log(opt)
    }
  }
}
</script>
  1. 测试效果


    最终效果

三、使用说明

  1. columns
    列属性,要求是一个数组
    text: 显示在表头的文字
    value: 对应data的key。treeTable将显示相应的value
    width: 每列的宽度,为一个数字(可选)
  2. expandAll
    是否默认全部展开,boolean值,默认为false
  3. evalFunc
    解析函数,function,非必须
    如果不提供,将使用默认的evalFunc
  4. evalArgs
    解析函数的参数,是一个数组
    请注意,自定义的解析函数参数第一个为this.data,第二个参数为, this.expandAll,你不需要在evalArgs填写。一定记住,这两个参数是强制性的,并且位置不可颠倒 this.data为需要解析的数据,this.expandAll为是否默认展开。
    如你的解析函数需要的参数为(this.data, this.expandAll,1,2,3,4),那么你只需要将[1,2,3,4]赋值给evalArgs就可以了
    如果你的解析函数参数只有(this.data, this.expandAll),那么就可以不用填写evalArgs了。
  5. slot
    这是一个自定义列的插槽。
    默认情况下,treeTable只有一行行展示数据的功能。但是一般情况下,我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式,这时我们就需要自定义列了。
    slot和columns属性可同时存在,columns里面的数据列会在slot自定义列的左边展示

三、树形数据(2.11.1)

https://element.eleme.io/#/zh-CN/component/table

<template>
  <div>
    <el-table
      :data="tableData"
      style="width: 100%;margin-bottom: 20px;"
      row-key="id"
      border
      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    >
      <el-table-column prop="name" label="名称" sortable width="180"></el-table-column>
    </el-table>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tableData: [
        {
          children: [
            {
              name: "菜单管理",
              id: "060ac069ea5b1b1b90e200d94f7941a2"
            },
            {
              name: "字典管理",
              id: "68aa1ca036ef8e6d606651f258321f88"
            },
            {
              children: [
                {
                  name: "删除",
                  id: "0e64c997bcec402b7a840fc18e7e4e82"
                },
                {
                  name: "查询",
                  id: "52ae959c875c6fc521ba4ea546ee88ed"
                },
                {
                  name: "添加",
                  id: "59c86152b4e0b345636eb3a48d9ad263"
                },
                {
                  name: "导出",
                  id: "66d8a79be81d61d4e75feccfe2aee94e"
                },
                {
                  name: "更新",
                  id: "acc80082612d67847940985dfe53268b"
                },
                {
                  name: "导入",
                  id: "e5668f14d71db22cb2ad063c87f0b6d3"
                }
              ],
              name: "角色管理",
              id: "951581fdede1c585a5b7f2de16eddfe2"
            },
            {
              name: "组织机构",
              id: "3504711eedb26cab2b9eb2f8c3433aa7"
            },
            {
              name: "用户管理",
              id: "6221da7dac0e80af56d18f9355003438"
            }
          ],
          name: "系统设置",
          id: "0b423292e0e27485cdd5257be6701a9f"
        }
      ]
    };
  },
};
</script>
结果展现

说明:
支持树类型的数据的显示。当 row 中包含 children 字段时,被视为树形数据。渲染树形数据时,必须要指定 row-key。支持子节点数据异步加载。设置 Table 的 lazy 属性为 true 与加载函数 load 。通过指定 row 中的 hasChildren 字段来指定哪些行是包含子节点。children 与 hasChildren 都可以通过 tree-props 配置。
新版功能更简单,更好使用。
注意:
实际测试,hasChildren 属性不添加也可,注意id不要重复,default-expand-all 属性无需设置值,添加该属性全部展开,不添加折叠。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容