我总共百度了三种方法,本来使用插件各种报错,不得已自己重写了一个表格,后面有空闲时间了,自己研究了下,这些插件都是可行的,只是要注意下使用的姿势

方法一:使用TreeGrid组件

这一种是基于iview的,所以在使用之前装好iview

git地址: https://github.com/huanglong6828/vue-tree-grid

仿照方法,将TreeGrid.vue直接复制进项目里面

在这里插入图片描述

代码如下:

<template>
    <div :style="{width:tableWidth}" class='autoTbale'>
        <table class="table table-bordered" id='hl-tree-table'>
            <thead>
                <tr>
                    <th v-for="(column,index) in cloneColumns">
                        <label v-if="column.type === 'selection'">
                            <input type="checkbox" v-model="checks" @click="handleCheckAll">
                        </label>
                        <label v-else>
                            {{ renderHeader(column, index) }}
                            <span class="ivu-table-sort" v-if="column.sortable">
                                <Icon type="arrow-up-b" :class="{on: column._sortType === 'asc'}" @click.native="handleSort(index, 'asc')" />
                                <Icon type="arrow-down-b" :class="{on: column._sortType === 'desc'}" @click.native="handleSort(index, 'desc')" />
                            </span>
                        </label>
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(item,index) in initItems" :key="item.id" v-show="show(item)" :class="{'child-tr':item.parent}">
                    <td v-for="(column,snum) in columns" :key="column.key" :style=tdStyle(column)>
                        <label v-if="column.type === 'selection'">
                            <input type="checkbox" :value="item.id" v-model="checkGroup" @click="handleCheckClick(item,$event,index)">
                        </label>
                        <div v-if="column.type === 'action'">
                            <i-button :type="action.type" size="small" @click="RowClick(item,$event,index,action.text)" v-for='action in (column.actions)' :key="action.text">{{action.text}}</i-button>
                        </div>
                        <label @click="toggle(index,item)" v-if="!column.type">
                            <span v-if='snum==iconRow()'>
                                <i v-html='item.spaceHtml'></i>
                                <i v-if="item.children&&item.children.length>0" class="ivu-icon" :class="{'ivu-icon-plus-circled':!item.expanded,'ivu-icon-minus-circled':item.expanded }"></i>
                                <i v-else class="ms-tree-space"></i>
                            </span> {{renderBody(item,column) }}
                        </label>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</template>
<script>
export default {
    name: 'treeGrid',
    props: {
        columns: Array,
        items: {
            type: Array,
            default: function() {
                return [];
            }
        }
    },
    data() {
        return {
            initItems: [], //处理后数据数组
            cloneColumns: [], //处理后的表头数据
            checkGroup: [], //复选框数组
            checks: false, //全选
            screenWidth: document.body.clientWidth, //自适应宽
            tdsWidth: 0, //td总宽
            timer: false, //控制监听时长
            dataLength: 0, //树形数据长度
        }
    },
    computed: {
        tableWidth() {
            return this.tdsWidth > this.screenWidth && this.screenWidth > 0 ? this.screenWidth + 'px' : '100%'
        }
    },
    watch: {
        screenWidth(val) {
            if (!this.timer) {
                this.screenWidth = val
                this.timer = true
                setTimeout(() => {
                    this.timer = false
                }, 400)
            }
        },
        items() {
            if (this.items) {
                this.dataLength = this.Length(this.items)
                this.initData(this.deepCopy(this.items), 1, null);
                this.checkGroup = this.renderCheck(this.items)
                if (this.checkGroup.length == this.dataLength) {
                    this.checks = true
                } else {
                    this.checks = false
                }
            }
        },
        columns: {
            handler() {
                this.cloneColumns = this.makeColumns();
            },
            deep: true
        },
        checkGroup(data) {
            this.checkAllGroupChange(data)
        },
    },
    mounted() {
        if (this.items) {
            this.dataLength = this.Length(this.items)
            this.initData(this.deepCopy(this.items), 1, null);
            this.cloneColumns = this.makeColumns();
            this.checkGroup = this.renderCheck(this.items)
            if (this.checkGroup.length == this.dataLength) {
                this.checks = true
            } else {
                this.checks = false
            }
        }
        // 绑定onresize事件 监听屏幕变化设置宽
        this.$nextTick(() => {
            this.screenWidth = document.body.clientWidth
        })
        window.onresize = () => {
            return (() => {
                window.screenWidth = document.body.clientWidth
                this.screenWidth = window.screenWidth
            })()
        }
    },
    methods: {
      // 有无多选框折叠位置优化
      iconRow() {
        for (var i = 0, len = this.columns.length; i < len; i++) {
          if (this.columns[i].type == 'selection') {
            return 1
          }
        }
        return 0
      },
        // 设置td宽度,td的align
        tdStyle(column) {
            var style = {}
            if (column.align) {
                style["text-align"] = column.align;
            }
            if (column.width) {
                style["min-width"] = column.width + 'px';
            }
            return style;
        },
        // 排序事件
        handleSort(index, type) {
            this.cloneColumns.forEach((col) => col._sortType = 'normal');
            if (this.cloneColumns[index]._sortType === type) {
                this.cloneColumns[index]._sortType = 'normal'
            } else {
                this.cloneColumns[index]._sortType = type
            }
            this.$emit('on-sort-change', this.cloneColumns[index]['key'], this.cloneColumns[index]['_sortType'])
        },
        // 点击某一行事件
        RowClick(data, event, index, text) {
            let result = this.makeData(data)
            this.$emit('on-row-click', result, event, index, text)
        },
        // 点击事件 返回数据处理
        makeData(data) {
            const t = this.type(data);
            let o;
            if (t === 'array') {
                o = [];
            } else if (t === 'object') {
                o = {};
            } else {
                return data;
            }
            if (t === 'array') {
                for (let i = 0; i < data.length; i++) {
                    o.push(this.makeData(data[i]));
                }
            } else if (t === 'object') {
                for (let i in data) {
                    if (i != 'spaceHtml' && i != 'parent' && i != 'level' && i != 'expanded' && i != 'isShow' && i !=
                        'load') {
                        o[i] = this.makeData(data[i]);
                    }
                }
            }
            return o;
        },
        // 处理表头数据
        makeColumns() {
            let columns = this.deepCopy(this.columns);
            this.tdsWidth = 0
            columns.forEach((column, index) => {
                column._index = index;
                column._width = column.width ? column.width : '';
                column._sortType = 'normal';
                this.tdsWidth += column.width ? parseFloat(column.width) : 0;
            });
            return columns;
        },
        // 数据处理 增加自定义属性监听
        initData(items, level, parent) {
            this.initItems = []
            let spaceHtml = "";
            for (var i = 1; i < level; i++) {
                spaceHtml += "<i class='ms-tree-space'></i>"
            }
            items.forEach((item, index) => {
                item = Object.assign({}, item, {
                    "parent": parent,
                    "level": level,
                    "spaceHtml": spaceHtml
                });
                if ((typeof item.expanded) == "undefined") {
                    item = Object.assign({}, item, {
                        "expanded": false
                    });
                }
                if ((typeof item.show) == "undefined") {
                    item = Object.assign({}, item, {
                        "isShow": false
                    });
                }
                if ((typeof item.isChecked) == "undefined") {
                    item = Object.assign({}, item, {
                        "isChecked": false
                    });
                }
                item = Object.assign({}, item, {
                    "load": (item.expanded ? true : false)
                });
                this.initItems.push(item);
                if (item.children && item.expanded) {
                    this.initData(item.children, level + 1, item);
                }
            })
        },
        //  隐藏显示
        show(item) {
            return ((item.level == 1) || (item.parent && item.parent.expanded && item.isShow));
        },
        toggle(index, item) {
            let level = item.level + 1;
            let spaceHtml = "";
            for (var i = 1; i < level; i++) {
                spaceHtml += "<i class='ms-tree-space'></i>"
            }
            if (item.children) {
                if (item.expanded) {
                    item.expanded = !item.expanded;
                    this.close(index, item);
                } else {
                    item.expanded = !item.expanded;
                    if (item.load) {
                        this.open(index, item);
                    } else {
                        item.load = true;
                        item.children.forEach((child, childIndex) => {
                            this.initItems.splice((index + childIndex + 1), 0, child);
                            //设置监听属性
                            this.$set(this.initItems[index + childIndex + 1], 'parent', item);
                            this.$set(this.initItems[index + childIndex + 1], 'level', level);
                            this.$set(this.initItems[index + childIndex + 1], 'spaceHtml', spaceHtml);
                            this.$set(this.initItems[index + childIndex + 1], 'isShow', true);
                            this.$set(this.initItems[index + childIndex + 1], 'expanded', false);
                        })
                    }
                }
            }
        },
        open(index, item) {
            if (item.children) {
                item.children.forEach((child, childIndex) => {
                    child.isShow = true;
                    if (child.children && child.expanded) {
                        this.open(index + childIndex + 1, child);
                    }
                })
            }
        },
        close(index, item) {
            if (item.children) {
                item.children.forEach((child, childIndex) => {
                    child.isShow = false;
                    child.expanded = false;
                    if (child.children) {
                        this.close(index + childIndex + 1, child);
                    }
                })
            }
        },
        //点击check勾选框,判断是否有children节点 如果有就一并勾选
        handleCheckClick(data, event, index){
            data.isChecked = !data.isChecked;
            var arr = data.children;
            if(arr){
                if(data.isChecked){
                    this.checkGroup.push(data.id);
                    for (let i=0; i<arr.length; i++){
                        this.checkGroup.push(arr[i].id)
                    }
                }else {
                    for (var i=0; i<this.checkGroup.length; i++){
                        if(this.checkGroup[i] == data.id){
                            this.checkGroup.splice(i,1)
                        }
                        for (var j=0; j<arr.length; j++){
                            if(this.checkGroup[i] == arr[j].id){
                                this.checkGroup.splice(i,1);
                            }
                        }
                    }
                }
            }
        },
        //checkbox 全选 选择事件
        handleCheckAll() {
            this.checks = !this.checks;
            if (this.checks) {
                this.checkGroup = this.getArray(this.checkGroup.concat(this.All(this.items)))
            } else {
                this.checkGroup = []
            }
            // this.$emit('on-selection-change', this.checkGroup)
        },
        // 数组去重
        getArray(a) {
            var hash = {},
                len = a.length,
                result = [];
            for (var i = 0; i < len; i++) {
                if (!hash[a[i]]) {
                    hash[a[i]] = true;
                    result.push(a[i]);
                }
            }
            return result;
        },
        checkAllGroupChange(data) {
            if (this.dataLength > 0 && data.length === this.dataLength) {
                this.checks = true;
            } else {
                this.checks = false;
            }
            this.$emit('on-selection-change', this.checkGroup)
        },
        All(data) {
            let arr = []
            data.forEach((item) => {
                arr.push(item.id)
                if (item.children && item.children.length > 0) {
                    arr = arr.concat(this.All(item.children));
                }
            })
            return arr
        },
        // 返回树形数据长度
        Length(data) {
            let length = data.length
            data.forEach((child) => {
                if (child.children) {
                    length += this.Length(child.children)
                }
            })
            return length;
        },
        // 返回表头
        renderHeader(column, $index) {
            if ('renderHeader' in this.columns[$index]) {
                return this.columns[$index].renderHeader(column, $index);
            } else {
                return column.title || '#';
            }
        },
        // 返回内容
        renderBody(row, column, index) {
            return row[column.key]
        },
        // 默认选中
        renderCheck(data) {
            let arr = []
            data.forEach((item) => {
                if (item._checked) {
                    arr.push(item.id)
                }
                if (item.children && item.children.length > 0) {
                    arr = arr.concat(this.renderCheck(item.children));
                }
            })
            return arr
        },
        // 深度拷贝函数
        deepCopy(data) {
            var t = this.type(data),
                o, i, ni;
            if (t === 'array') {
                o = [];
            } else if (t === 'object') {
                o = {};
            } else {
                return data;
            }
            if (t === 'array') {
                for (i = 0, ni = data.length; i < ni; i++) {
                    o.push(this.deepCopy(data[i]));
                }
                return o;
            } else if (t === 'object') {
                for (i in data) {
                    o[i] = this.deepCopy(data[i]);
                }
                return o;
            }
        },
        type(obj) {
            var toString = Object.prototype.toString;
            var map = {
                '[object Boolean]': 'boolean',
                '[object Number]': 'number',
                '[object String]': 'string',
                '[object Function]': 'function',
                '[object Array]': 'array',
                '[object Date]': 'date',
                '[object RegExp]': 'regExp',
                '[object Undefined]': 'undefined',
                '[object Null]': 'null',
                '[object Object]': 'object'
            };
            return map[toString.call(obj)];
        }
    },
    beforeDestroy() {
        window.onresize = null
    }
}
</script>
<style>
.autoTbale {
    overflow: auto;
}
table {
    width: 100%;
    border-spacing: 0;
    border-collapse: collapse;
}
.table-bordered {
    border: 1px solid #EBEBEB;
}
.table>tbody>tr>td,
.table>tbody>tr>th,
.table>thead>tr>td,
.table>thead>tr>th {
    border-top: 1px solid #e7eaec;
    line-height: 1.42857;
    padding: 8px;
    vertical-align: middle;
}
.table-bordered>tbody>tr>td,
.table-bordered>tbody>tr>th,
.table-bordered>tfoot>tr>td,
.table-bordered>tfoot>tr>th,
.table-bordered>thead>tr>td,
.table-bordered>thead>tr>th {
    border: 1px solid #e7e7e7;
}
.table>thead>tr>th {
    border-bottom: 1px solid #DDD;
}
.table-bordered>thead>tr>td,
.table-bordered>thead>tr>th {
    background-color: #F5F5F6;
}
#hl-tree-table>tbody>tr {
    background-color: #fbfbfb;
}
#hl-tree-table>tbody>.child-tr {
    background-color: #fff;
}
label {
    margin: 0 8px;
}
.ms-tree-space {
    position: relative;
    top: 1px;
    display: inline-block;
    font-style: normal;
    font-weight: 400;
    line-height: 1;
    width: 14px;
    height: 14px;
}
.ms-tree-space::before {
    content: ""
}
#hl-tree-table th>label {
    margin: 0;
}
</style>

在HelloWorld.vue中使用:

<template>
    <tree-grid
        :items='data'
        :columns='columns'
        @on-row-click='rowClick'
        @on-selection-change='selectionClick'
        @on-sort-change='sortClick'
    >
    </tree-grid>
</template>

<script>
    import TreeGrid from './TreeGrid.vue';
    export default {
        components: { TreeGrid },
        data () {},//数据自行添加
		methods: {
            rowClick(data, index, event) {
                console.log('当前行数据:' + data)
                console.log('点击行号:' + index)
                console.log('点击事件:' + event)
            },
            selectionClick(arr) {
                console.log('选中数据id数组:' + arr)
            },
            sortClick(key, type) {
                console.log('排序字段:' + key)
                console.log('排序规则:' + type)
            }
        }
    }

不足之处在用户这种方法,我真的不知道最后操作里面按钮的方法应该写到哪里,可能水平有限欢迎大家指正。

在这里插入图片描述图标遗失,但是子项可显示

方法二:使用vue-table-with-tree-grid

同样基于iview,使用之前下载iview

使用方法及各项属性地址https://www.npmjs.com/package/vue-table-with-tree-grid

需要注意的是,我第一次使用就是无论主子项通通有children,只是子项children为空,但是这样会造成内存的溢出,也就是说它会一直一直去循环显示子项,不会停止。

贴出代码:

<template lang="html">
  <div id="example">
    <zk-table
      ref="table"
      sum-text="sum"
      index-text="序号"
      :data="data"
      :columns="columns"
      :stripe="props.stripe"
      :border="props.border"
      :show-header="props.showHeader"
      :show-summary="props.showSummary"
      :show-row-hover="props.showRowHover"
      :show-index="props.showIndex"
      :tree-type="props.treeType"
      :is-fold="props.isFold"
      :expand-type="props.expandType"
      :selection-type="props.selectionType">
      <template slot="handle" slot-scope="scope">
        <Button type="primary" @click="handleDo(scope.row)">编辑</Button>
      </template>
    </zk-table>
  </div>
</template>

<script>
  export default {
    name: 'example',
    data() {
      return {
        props: {
          stripe: false,
          border: false,
          showHeader: true,
          showSummary: false,
          showRowHover: true,
          showIndex: true,
          treeType: true,
          isFold: true,
          expandType: false,
          selectionType: true,
        },
        data: [ ],
        columns: [
          {
            label: '事项类型',
            prop: '****',
            // width: '400px',
          },
          {
            label: '事项名称',
            prop: '***',
            // minWidth: '50px',
          },
          {
            label: '实施编码',
            prop: '***',
          },
          {
            label: '部门',
            prop: '***',
            // minWidth: '200px',
            // type: 'template',
            // template: 'likes',
          },
          {
            label: '录入时间',
            prop: '***'
          },
          {
            label: '权力状态',
            prop: '***'
          },
          {
            label: '版本号',
            prop: '***'
          },
          {
            label: '操作',
            type: 'template',
            template: 'handle'
          }
        ],
      };
    },
    computed: {
      propList() {
        return Object.keys(this.props).map(item => ({
          name: item,
        }));
      }
    },
    methods: {
      handleDo(item){
        console.log(item);
      }
    },
  };
</script>

优点是可以把操作放到template中,用不同的命名来区分,还是比较方便
,公司数据不方便写出来。

在这里插入图片描述
方法三:重写表格

这个方法比较笨,是我自己是在没办法找到的方法

 <table class="subtable">
                <thead>
                  <tr>
                    <td rowspan="1" colspan="1"></td>
                    <td rowspan="1" colspan="1"><div class="cell" style="padding:0 14px;"><el-checkbox :indeterminate="allCheck" v-model="checkAll" @change="handleCheckAllChange"></el-checkbox></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip"><div>序号</div></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">事项类型</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip;">事项名称</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">实施编码</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">部门</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">录入时间</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">权力状态</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">版本号</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">操作</div></td>
                  </tr>
                </thead>
                <tbody v-for="(item,index) in tableData3">
                  <tr>
                    <td rowspan="1" colspan="1"><div class="cell" v-show="item.catalogItems.length != 0" @click="showOrNot(item,index)"><div class="el-table__expand-icon "><i :class="item.childrenShow?'el-icon-arrow-down':'el-icon-arrow-right'"></i></div></div></td>
                    <td rowspan="1" colspan="1">
                      <div class="cell" style="padding:0 14px;">
                        <el-checkbox v-model="item.checked" @change="handleChecked(index,item)"></el-checkbox>
                      </div>
                    </td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip"><div>{{index+1}}</div></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.taskType}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip;"><span class="listName">{{item.taskName}}</span></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.taskCode}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.deptName}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.createTime}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.qlState}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{item.taskVersion}}</div></td>
                    <td rowspan="1" colspan="10"><!----><!---->
                      <el-button
                          @click="jump"
                          type="text"
                          size="small">
                        </el-button>
                    </td>
                  </tr>
                  <tr class="child" v-for="(el,i) in item.catalogItems" v-show="item.childrenShow">
                    <td rowspan="1" colspan="1"><div class="cell"></div></td>
                    <td rowspan="1" colspan="1"><div class="cell" style="padding:0 14px;"></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip"><div>{{index+1}}.{{i+1}}</div></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip;"><span class="listName">{{el.***}}</span></div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="1"><div class="cell el-tooltip">{{el.***}}</div></td>
                    <td rowspan="1" colspan="10">
                        <el-button
                          @click="jump"
                          type="text"
                          size="small">
                        </el-button>
                    </td>
                  </tr>
                </tbody>
              </table>

思路是先将头部信息写成固定thead,然后使用tbody循环主项,同时判断如果有子项再次使用tbody标签循环出子项,这种方法其实破坏了标签的作用,也是不得已的方法。

//方法
//是否展示子项
showOrNot(item,index){
        // console.log(item);
        // // item.childrenShow = !item.childrenShow;
        // console.log(item);
        // console.log(item,index);
        // console.log(this.tableData);
        this.tableData3 = [];
        this.tableData.forEach((el,i) => {
          if(i == index){
            el.childrenShow = !el.childrenShow;
          }
          // console.log(el)
          this.tableData3.push(el);
        })
      }

在这里插入图片描述
自己重写的话样式可以自己选择,怎么好看怎么改

有问题可联系我,谢谢

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐