一、安装组件
npm i element-ui -S
二、创建组件
- 创建目录treeTable
- 创建数组转换文件
'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
}
- 创建组件
在目录中创建组件: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>
- 使用组件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>
-
测试效果
三、使用说明
- columns
列属性,要求是一个数组
text: 显示在表头的文字
value: 对应data的key。treeTable将显示相应的value
width: 每列的宽度,为一个数字(可选) - expandAll
是否默认全部展开,boolean值,默认为false - evalFunc
解析函数,function,非必须
如果不提供,将使用默认的evalFunc - 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了。 - 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 属性无需设置值,添加该属性全部展开,不添加折叠。