Table: add support tree structure data (#14632)

This commit is contained in:
hetech 2019-03-27 16:13:21 +08:00 committed by iamkun
parent a191ca8639
commit 9def85be4a
11 changed files with 937 additions and 42 deletions

View File

@ -1330,6 +1330,123 @@ When the row content is too long and you do not want to display the horizontal s
```
:::
### Tree data and lazy mode
:::demo You can display tree structure data。When using it, the prop `row-key` is required。Also, child row data can be loaded asynchronously. Set `lazy` property of Table to true and the function `load`. Specify `hasChildren` attribute in row to determine which row contains children.
```html
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
border
row-key="id">
<el-table-column
prop="date"
label="日期"
sortable
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
sortable
width="180">
</el-table-column>
</el-table>
<el-table
:data="tableData1"
style="width: 100%"
row-key="id"
border
lazy
:load="load"
>
<el-table-column
prop="date"
label="date"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
width="180">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
children: [{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}]
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}],
tableData1: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
hasChildren: true
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}]
}
},
methods: {
load(tree, treeNode, resolve) {
resolve([
{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}
])
}
},
}
</script>
```
:::
### Summary row
For table of numbers, you can add an extra row at the table footer displaying each column's sum.
@ -1706,7 +1823,7 @@ You can customize row index in `type=index` columns.
| header-row-style | function that returns custom style for a row in table header, or an object assigning custom style for every row in table header | Function({row, rowIndex})/Object | — | — |
| header-cell-class-name | function that returns custom class names for a cell in table header, or a string assigning class names for every cell in table header | Function({row, column, rowIndex, columnIndex})/String | — | — |
| header-cell-style | function that returns custom style for a cell in table header, or an object assigning custom style for every cell in table header | Function({row, column, rowIndex, columnIndex})/Object | — | — |
| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | Function(row)/String | — | — |
| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on or display tree data. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | Function(row)/String | — | — |
| empty-text | Displayed text when data is empty. You can customize this area with `slot="empty"` | String | — | No Data |
| default-expand-all | whether expand all rows by default, only works when the table has a column type="expand" | Boolean | — | false |
| expand-row-keys | set expanded rows by this prop, prop's value is the keys of expand rows, you should set row-key before using this prop | Array | — | |
@ -1717,6 +1834,9 @@ You can customize row index in `type=index` columns.
| summary-method | custom summary method | Function({ columns, data }) | — | — |
| span-method | method that returns rowspan and colspan | Function({ row, column, rowIndex, columnIndex }) | — | — |
| select-on-indeterminate | controls the behavior of master checkbox in multi-select tables when only some rows are selected (but not all). If true, all rows will be selected, else deselected. | Boolean | — | true |
| indent | horizontal indentation of tree data | Number | — | 16 |
| lazy | whether to lazy loading data | Boolean| — | — |
| load | method for loading child row data, only works when `lazy` is true | Function({ row, treeNode, resolve }) | — | — |
### Table Events
| Event Name | Description | Parameters |

View File

@ -1332,6 +1332,123 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
```
:::
### Tree data and lazy mode
:::demo You can display tree structure data。When using it, the prop `row-key` is required。Also, child row data can be loaded asynchronously. Set `lazy` property of Table to true and the function `load`. Specify `hasChildren` attribute in row to determine which row contains children.
```html
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
border
row-key="id">
<el-table-column
prop="date"
label="日期"
sortable
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
sortable
width="180">
</el-table-column>
</el-table>
<el-table
:data="tableData1"
style="width: 100%"
row-key="id"
border
lazy
:load="load"
>
<el-table-column
prop="date"
label="date"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
width="180">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
children: [{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}]
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}],
tableData1: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
hasChildren: true
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}]
}
},
methods: {
load(tree, treeNode, resolve) {
resolve([
{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}
])
}
},
}
</script>
```
:::
### Fila de resumen
Para una tabla de números, puede agregar una fila extra en el pie de página de la tabla que muestra la suma de cada columna.
@ -1711,7 +1828,7 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
| header-row-style | función que devuelve estilos personalizados para una fila en la cabecera de la tabla, o un objeto asignando estilos personalizados para cada fila en la cabecera de la tabla | Function({row, rowIndex})/Object | — | — |
| header-cell-class-name | función que devuelve nombre de clases personalizadas para una celda en la cabecera de la tabla, o una cadena asignando nombres de clases para cada celda en la cabecera de la tabla | Function({row, column, rowIndex, columnIndex})/String | — | — |
| header-cell-style | función que devuelve estilos personalizados para una celda en la cabecera de la tabla, o un objeto asignando estilos personalizados para cada celda en la cabecera de la tabla | Function({row, column, rowIndex, columnIndex})/Object | — | — |
| row-key | clave de datos de la fila, utilizada para optimizar la representación de los datos. Es obligatorio `reserve-selection` esta habilitado. Cuando su tipo es string, se permite el acceso multinivel, por ejemplo, `user.info.id`, pero `user.info[0].id` no es permitido, en cuyo caso se debe usar una `function` | Function(row)/String | — | — |
| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on or display tree data. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | Function(row)/String | — | — |
| empty-text | Texto mostrado cuando no existen datos. Puede personalizar esta área con `slot="empty"` | String | — | No Data |
| default-expand-all | especifica si todas las filas se expanden por defeto, solo funciona cuando la tabla tiene una columna `type="expand"` | Boolean | — | false |
| expand-row-keys | establece las filas expandidas a través de esta propiedad, este valor es la clave de filas expandidas, debería establecer `row-key` antes de usar esta propiedad | Array | — | |
@ -1721,7 +1838,10 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
| sum-text | texto a mostrar para la primer columna de la fila de resumen | String | — | Sum |
| summary-method | método personalizado para resumen | Function({ columns, data }) | — | — |
| span-method | método que devuelve _rowspan_ y _colspan_ | Function({ row, column, rowIndex, columnIndex }) | — | — |
| select-on-indeterminate | controla el comportamiento del checkbox maestro en tablas de selección múltiple cuando sólo se seleccionan algunas filas (pero no todas). Si es true, todas las filas serán seleccionadas, de lo contrario deseleccionadas. | Boolean | — | true |
| select-on-indeterminate | controla el comportamiento del checkbox maestro en tablas de selección múltiple cuando sólo se seleccionan algunas filas (pero no todas). Si es true, todas las filas serán seleccionadas, de lo contrario deseleccionadas. | Boolean | — | true |
| indent | horizontal indentation of tree data | Number | — | 16 |
| lazy | whether to lazy loading data | Boolean | — | — |
| load | method for loading child row data, only works when `lazy` is true | Function({ row, treeNode, resolve }) | — | — |
### Eventos de la tabla
| Nombre del evento | Descripción | Parámetros |

View File

@ -1333,6 +1333,123 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
```
:::
### Tree data and lazy mode
:::demo You can display tree structure data。When using it, the prop `row-key` is required。Also, child row data can be loaded asynchronously. Set `lazy` property of Table to true and the function `load`. Specify `hasChildren` attribute in row to determine which row contains children.
```html
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
border
row-key="id">
<el-table-column
prop="date"
label="日期"
sortable
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
sortable
width="180">
</el-table-column>
</el-table>
<el-table
:data="tableData1"
style="width: 100%"
row-key="id"
border
lazy
:load="load"
>
<el-table-column
prop="date"
label="date"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="name"
width="180">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
children: [{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}]
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}],
tableData1: [{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu'
}, {
id: 2,
date: '2016-05-04',
name: 'wangxiaohu'
}, {
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
hasChildren: true
}, {
id: 4,
date: '2016-05-03',
name: 'wangxiaohu'
}]
}
},
methods: {
load(tree, treeNode, resolve) {
resolve([
{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu'
}, {
id: 32,
date: '2016-05-01',
name: 'wangxiaohu'
}
])
}
},
}
</script>
```
:::
### Ligne de somme
Pour les tableaux de nombres, vous pouvez ajouter une ligne en plus pour afficher la somme de chaque colonne.
@ -1713,7 +1830,7 @@ Vous pouvez personnaliser les indices des colonnes de type `index`.
| header-row-style | Fonction qui retourne un style pour chaque ligne de header. Peut aussi être un objet assignant un style à chaque ligne de header. | Function({row, rowIndex})/Object | — | — |
| header-cell-class-name | Fonction qui retourne un nom de classe pour chaque cellule de header. Peut aussi être une simple chaîne de caractères assignant une classe à chaque cellule de header. | Function({row, column, rowIndex, columnIndex})/String | — | — |
| header-cell-style | Fonction qui retourne un style pour chaque cellule de header. Peut aussi être un objet assignant un style à chaque cellule de header. | Function({row, column, rowIndex, columnIndex})/Object | — | — |
| row-key | Clé de chaque ligne, utilisée pour optimiser le rendu. Requise si `reserve-selection` est activé. Quand c'est un `String`, l'accès multi-niveaux est supporté, e.g. `user.info.id`, mais `user.info[0].id` n'est pas supporté. Dans ce dernier cas une `Function` devrait être utilisée. | Function(row)/String | — | — |
| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on or display tree data. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | Function(row)/String | — | — |
| empty-text | Texte à afficher quand il n'y a pas de données. Vous pouvez changer cette zone grâce à `slot="empty"`. | String | — | No Data |
| default-expand-all | Si toutes les lignes sont étendues par défaut, ne marche que si des lignes ont type="expand". | Boolean | — | false |
| expand-row-keys | Détermine les lignes qui sont étendues, contient les clés des lignes correspondantes. Vous devriez configurer `row-key` avant celle-ci. | Array | — | |
@ -1724,6 +1841,9 @@ Vous pouvez personnaliser les indices des colonnes de type `index`.
| summary-method | La méthode pour calculer la somme. | Function({ columns, data }) | — | — |
| span-method | Méthode qui retourne les valeurs de colspan et rowspan. | Function({ row, column, rowIndex, columnIndex }) | — | — |
| select-on-indeterminate | Contrôle le comportement de la checkbox globale dans les tables avec sélection multiple lorsque seulement certaines lignes sont sélectionnées. Si `true`, toutes les lignes sont sélectionnées. | Boolean | — | true |
| indent | horizontal indentation of tree data | Number | — | 16 |
| lazy | whether to lazy loading data | Boolean | — | — |
| load | method for loading child row data, only works when `lazy` is true | Function({ row, treeNode, resolve }) | — | — |
### Évènements de Table

View File

@ -1275,6 +1275,143 @@
```
:::
### 树形数据与懒加载
:::demo 支持树类型的数据。此时,必须要指定 `row-key`。支持子节点数据异步加载。设置 Table 的 `lazy` 属性为 true 与 加载函数 `load` ,指定 row 中的 `hasChildren` 来确定哪些行是包含子节点。
```html
<template>
<div>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
border
row-key="id">
<el-table-column
prop="date"
label="日期"
sortable
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
sortable
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
<el-table
:data="tableData1"
style="width: 100%"
row-key="id"
border
lazy
:load="load"
>
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}]
}, {
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}],
tableData1: [{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
hasChildren: true
}, {
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}]
}
},
methods: {
load(tree, treeNode, resolve) {
resolve([
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
])
}
},
}
</script>
```
:::
### 自定义表头
表头支持自定义。
@ -1728,7 +1865,7 @@
| header-row-style | 表头行的 style 的回调方法,也可以使用一个固定的 Object 为所有表头行设置一样的 Style。 | Function({row, rowIndex})/Object | — | — |
| header-cell-class-name | 表头单元格的 className 的回调方法,也可以使用字符串为所有表头单元格设置一个固定的 className。 | Function({row, column, rowIndex, columnIndex})/String | — | — |
| header-cell-style | 表头单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有表头单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object | — | — |
| row-key | 行数据的 Key用来优化 Table 的渲染;在使用 reserve-selection 功能的情况下,该属性是必填的。类型为 String 时,支持多层访问:`user.info.id`,但不支持 `user.info[0].id`,此种情况请使用 `Function`。 | Function(row)/String | — | — |
| row-key | 行数据的 Key用来优化 Table 的渲染;在使用 reserve-selection 功能与显示树形数据时,该属性是必填的。类型为 String 时,支持多层访问:`user.info.id`,但不支持 `user.info[0].id`,此种情况请使用 `Function`。 | Function(row)/String | — | — |
| empty-text | 空数据时显示的文本内容,也可以通过 `slot="empty"` 设置 | String | — | 暂无数据 |
| default-expand-all | 是否默认展开所有行,当 Table 中存在 type="expand" 的 Column 的时候有效 | Boolean | — | false |
| expand-row-keys | 可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。| Array | — | |
@ -1739,6 +1876,9 @@
| summary-method | 自定义的合计计算方法 | Function({ columns, data }) | — | — |
| span-method | 合并行或列的计算方法 | Function({ row, column, rowIndex, columnIndex }) | — | — |
| select-on-indeterminate | 在多选表格中,当仅有部分行被选中时,点击表头的多选框时的行为。若为 true则选中所有行若为 false则取消选择所有行 | Boolean | — | true |
| indent | 展示树形数据时,树节点的缩进 | Number | — | 16 |
| lazy | 是否懒加载子节点数据 | Boolean | — | — |
| load | 加载子节点数据的函数lazy 为 true 时生效 | Function({ row, treeNode, resolve }) | — | — |
### Table Events
| 事件名 | 说明 | 参数 |

View File

@ -29,6 +29,29 @@ export default {
render(h) {
const columnsHidden = this.columns.map((column, index) => this.isColumnHidden(index));
let rows = this.data;
if (this.store.states.lazy && Object.keys(this.store.states.lazyTreeNodeMap).length) {
rows = rows.reduce((prev, item) => {
prev.push(item);
const rowKey = this.store.table.getRowKey(item);
const parent = this.store.states.treeData[rowKey];
if (parent && parent.children) {
const tmp = [];
const traverse = (children) => {
if (!children) return;
children.forEach(key => {
tmp.push(this.store.states.lazyTreeNodeMap[key]);
if (this.store.states.treeData[key]) {
traverse(this.store.states.treeData[key].children);
}
});
};
traverse(parent.children);
prev = prev.concat(tmp);
}
return prev;
}, []);
}
return (
<table
class="el-table__body"
@ -42,22 +65,46 @@ export default {
</colgroup>
<tbody>
{
this._l(this.data, (row, $index) =>
[<tr
this._l(rows, (row, $index) => {
const rowKey = this.table.rowKey ? this.getKeyOfRow(row, $index) : $index;
const treeNode = this.treeData[rowKey];
const rowClasses = this.getRowClass(row, $index);
if (treeNode) {
rowClasses.push('el-table__row--level-' + treeNode.level);
}
const tr = (<tr
v-show={ treeNode ? treeNode.display : true }
style={ this.rowStyle ? this.getRowStyle(row, $index) : null }
key={ this.table.rowKey ? this.getKeyOfRow(row, $index) : $index }
key={ rowKey }
on-dblclick={ ($event) => this.handleDoubleClick($event, row) }
on-click={ ($event) => this.handleClick($event, row) }
on-contextmenu={ ($event) => this.handleContextMenu($event, row) }
on-mouseenter={ _ => this.handleMouseEnter($index) }
on-mouseleave={ _ => this.handleMouseLeave() }
class={ [this.getRowClass(row, $index)] }>
class={ rowClasses }>
{
this._l(this.columns, (column, cellIndex) => {
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
if (!rowspan || !colspan) {
return '';
} else {
const data = {
store: this.store,
_self: this.context || this.table.$vnode.context,
row,
column,
$index
};
if (cellIndex === this.firstDefaultColumnIndex && treeNode) {
data.treeNode = {
hasChildren: treeNode.hasChildren || (treeNode.children && treeNode.children.length),
expanded: treeNode.expanded,
indent: treeNode.level * this.treeIndent,
level: treeNode.level,
loaded: treeNode.loaded,
rowKey
};
}
return (
<td
style={ this.getCellStyle($index, cellIndex, row, column) }
@ -70,13 +117,7 @@ export default {
column.renderCell.call(
this._renderProxy,
h,
{
row,
column,
$index,
store: this.store,
_self: this.context || this.table.$vnode.context
},
data,
columnsHidden[cellIndex]
)
}
@ -85,16 +126,20 @@ export default {
}
})
}
</tr>,
this.store.isRowExpanded(row)
? (<tr>
<td colspan={ this.columns.length } class="el-table__expanded-cell">
{ this.table.renderExpanded ? this.table.renderExpanded(h, { row, $index, store: this.store }) : ''}
</td>
</tr>)
: ''
]
).concat(
</tr>);
if (this.store.isRowExpanded(row)) {
return [
tr,
<tr>
<td colspan={ this.columns.length } class="el-table__expanded-cell">
{ this.table.renderExpanded ? this.table.renderExpanded(h, { row, $index, store: this.store }) : ''}
</td>
</tr>
];
} else {
return tr;
}
}).concat(
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
)
}
@ -112,6 +157,10 @@ export default {
return this.store.states.data;
},
treeData() {
return this.store.states.treeData;
},
columnsCount() {
return this.store.states.columns.length;
},
@ -134,6 +183,19 @@ export default {
columns() {
return this.store.states.columns;
},
firstDefaultColumnIndex() {
for (let index = 0; index < this.columns.length; index++) {
if (this.columns[index].type === 'default') {
return index;
}
}
return 0;
},
treeIndent() {
return this.store.states.indent;
}
},
@ -232,7 +294,7 @@ export default {
classes.push('expanded');
}
return classes.join(' ');
return classes;
},
getCellStyle(rowIndex, columnIndex, row, column) {

View File

@ -326,10 +326,16 @@ export default {
if (!renderCell) {
renderCell = DEFAULT_RENDER_CELL;
}
const children = [
_self.renderTreeCell(data),
renderCell(h, data)
];
return _self.showOverflowTooltip || _self.showTooltipWhenOverflow
? <div class="cell el-tooltip" style={ {width: (data.column.realWidth || data.column.width) - 1 + 'px'} }>{ renderCell(h, data) }</div>
: <div class="cell">{ renderCell(h, data) }</div>;
? <div class="cell el-tooltip" style={ {width: (data.column.realWidth || data.column.width) - 1 + 'px'} }>{ children }</div>
: (<div class="cell">
{ children }
</div>);
};
},
@ -438,6 +444,32 @@ export default {
}
},
methods: {
renderTreeCell(data) {
if (!data.treeNode) return null;
const ele = [];
ele.push(<span class="el-table__indent" style={{'padding-left': data.treeNode.indent + 'px'}}></span>);
if (data.treeNode.hasChildren) {
ele.push(<div class={ ['el-table__expand-icon', data.treeNode.expanded ? 'el-table__expand-icon--expanded' : '']}
on-click={this.handleTreeExpandIconClick.bind(this, data)}>
<i class='el-icon el-icon-arrow-right'></i>
</div>);
} else {
ele.push(<span class="el-table__placeholder"></span>);
}
return ele;
},
handleTreeExpandIconClick(data, e) {
e.stopPropagation();
if (data.store.states.lazy && !data.treeNode.loaded) {
data.store.loadData(data.row, data.treeNode);
} else {
data.store.toggleTreeExpansion(data.treeNode.rowKey);
}
}
},
mounted() {
const owner = this.owner;
const parent = this.columnOrTableParent;

View File

@ -8,7 +8,41 @@ const sortData = (data, states) => {
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data;
}
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
if (Object.keys(states.treeData).length === 0) {
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
}
// 存在嵌套类型的数据
const rowKey = states.rowKey;
const filteredData = [];
const treeDataMap = {};
let index = 0;
while (index < data.length) {
let cur = data[index];
const key = cur[rowKey];
let treeNode = states.treeData[key];
filteredData.push(cur);
index++;
if (!treeNode) {
continue;
}
treeDataMap[key] = [];
while (index < data.length) {
cur = data[index];
treeNode = states.treeData[cur[rowKey]];
index++;
if (treeNode && treeNode.level !== 0) {
treeDataMap[key].push(cur);
} else {
filteredData.push(cur);
break;
}
}
}
const sortedData = orderBy(filteredData, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
return sortedData.reduce((prev, current) => {
const treeNodes = treeDataMap[current[rowKey]] || [];
return prev.concat(current, treeNodes);
}, []);
};
const getKeysMap = function(array, rowKey) {
@ -108,7 +142,11 @@ const TableStore = function(table, initialState = {}) {
filters: {},
expandRows: [],
defaultExpandAll: false,
selectOnIndeterminate: false
selectOnIndeterminate: false,
treeData: {},
indent: 16,
lazy: false,
lazyTreeNodeMap: {}
};
this._toggleAllSelection = debounce(10, function(states) {
@ -644,4 +682,70 @@ TableStore.prototype.commit = function(name, ...args) {
}
};
TableStore.prototype.toggleTreeExpansion = function(rowKey) {
const { treeData } = this.states;
const node = treeData[rowKey];
if (!node) return;
if (typeof node.expanded !== 'boolean') {
throw new Error('a leaf must have expanded property');
}
node.expanded = !node.expanded;
let traverse = null;
if (node.expanded) {
traverse = (children, parent) => {
if (children && parent.expanded) {
children.forEach(key => {
treeData[key].display = true;
traverse(treeData[key].children, treeData[key]);
});
}
};
node.children.forEach(key => {
treeData[key].display = true;
traverse(treeData[key].children, treeData[key]);
});
} else {
const traverse = (children) => {
if (!children) return;
children.forEach(key => {
treeData[key].display = false;
traverse(treeData[key].children);
});
};
traverse(node.children);
}
};
TableStore.prototype.loadData = function(row, treeNode) {
const table = this.table;
const parentRowKey = treeNode.rowKey;
if (table.lazy && table.load) {
table.load(row, treeNode, (data) => {
if (!Array.isArray(data)) {
throw new Error('data must be an array');
}
const treeData = this.states.treeData;
data.forEach(item => {
const rowKey = table.getRowKey(item);
const parent = treeData[parentRowKey];
parent.loaded = true;
parent.children.push(rowKey);
const child = {
display: true,
level: parent.level + 1
};
if (item.hasChildren) {
child.expanded = false;
child.hasChildren = true;
child.children = [];
}
Vue.set(treeData, rowKey, child);
Vue.set(this.states.lazyTreeNodeMap, rowKey, item);
});
this.toggleTreeExpansion(parentRowKey);
});
}
};
export default TableStore;

View File

@ -224,6 +224,26 @@
import TableBody from './table-body';
import TableHeader from './table-header';
import TableFooter from './table-footer';
import { getRowIdentity } from './util';
const flattenData = function(data) {
if (!data) return data;
let newData = [];
const flatten = arr => {
arr.forEach((item) => {
newData.push(item);
if (Array.isArray(item.children)) {
flatten(item.children);
}
});
};
flatten(data);
if (data.length === newData.length) {
return data;
} else {
return newData;
}
};
let tableIdSeed = 1;
@ -311,7 +331,16 @@
selectOnIndeterminate: {
type: Boolean,
default: true
}
},
indent: {
type: Number,
default: 16
},
lazy: Boolean,
load: Function
},
components: {
@ -451,6 +480,55 @@
toggleAllSelection() {
this.store.commit('toggleAllSelection');
},
getRowKey(row) {
const rowKey = getRowIdentity(row, this.store.states.rowKey);
if (!rowKey) {
throw new Error('if there\'s nested data, rowKey is required.');
}
return rowKey;
},
getTableTreeData(data) {
const treeData = {};
const traverse = (children, parentData, level) => {
children.forEach(item => {
const rowKey = this.getRowKey(item);
treeData[rowKey] = {
display: false,
level
};
parentData.children.push(rowKey);
if (Array.isArray(item.children) && item.children.length) {
treeData[rowKey].children = [];
treeData[rowKey].expanded = false;
traverse(item.children, treeData[rowKey], level + 1);
}
});
};
if (data) {
data.forEach(item => {
const containChildren = Array.isArray(item.children) && item.children.length;
if (!(containChildren || item.hasChildren)) return;
const rowKey = this.getRowKey(item);
const treeNode = {
level: 0,
expanded: false,
display: true,
children: []
};
if (containChildren) {
treeData[rowKey] = treeNode;
traverse(item.children, treeData[rowKey], 1);
} else if (item.hasChildren && this.lazy) {
treeNode.hasChildren = true;
treeNode.loaded = false;
treeData[rowKey] = treeNode;
}
});
}
return treeData;
}
},
@ -582,6 +660,8 @@
data: {
immediate: true,
handler(value) {
this.store.states.treeData = this.getTableTreeData(value);
value = flattenData(value);
this.store.commit('setData', value);
if (this.$ready) {
this.$nextTick(() => {
@ -633,7 +713,9 @@
const store = new TableStore(this, {
rowKey: this.rowKey,
defaultExpandAll: this.defaultExpandAll,
selectOnIndeterminate: this.selectOnIndeterminate
selectOnIndeterminate: this.selectOnIndeterminate,
indent: this.indent,
lazy: this.lazy
});
const layout = new TableLayout({
store,

View File

@ -76,6 +76,11 @@
}
}
@include e(placeholder) {
display: inline-block;
width: 20px;
}
@include m(fit) {
border-right: 0;
border-bottom: 0;
@ -552,4 +557,13 @@
overflow: hidden;
}
}
[class*=el-table__row--level] {
.el-table__expand-icon {
display: inline-block;
width: 14px;
vertical-align: middle;
margin-right: 5px;
}
}
}

View File

@ -676,7 +676,10 @@ describe('Table', () => {
const vm = createVue({
template: `
<el-table :data="testData">
<el-table-column prop="name" :render-header="renderHeader" label="name">
<el-table-column prop="name" label="name">
<template slot="header" slot-scope="{ column, $index }">
{{ $index }}:{{column.label}}
</template>
</el-table-column>
<el-table-column prop="release"/>
<el-table-column prop="director"/>
@ -684,12 +687,6 @@ describe('Table', () => {
</el-table>
`,
methods: {
renderHeader(h, { column, $index }) {
return '' + $index + ':' + column.label;
}
},
created() {
this.testData = getTestData();
}
@ -697,7 +694,7 @@ describe('Table', () => {
setTimeout(_ => {
const headerCell = vm.$el.querySelector('.el-table__header-wrapper thead tr th:first-child .cell');
expect(headerCell.textContent).to.equal('0:name');
expect(headerCell.textContent.trim()).to.equal('0:name');
destroyVM(vm);
done();
}, DELAY);
@ -1882,7 +1879,7 @@ describe('Table', () => {
it('keep highlight row after sort', done => {
const vm = createVue({
template: `
<el-table :data="testData" highlight-current-row row-key="release">
<el-table :data="testData" row-key="release" highlight-current-row >
<el-table-column prop="name" label="片名" />
<el-table-column prop="release" label="发行日期" />
<el-table-column prop="director" label="导演" />
@ -1912,4 +1909,91 @@ describe('Table', () => {
}, DELAY);
}, DELAY);
});
it('render tree structual data', (done) => {
const vm = createVue({
template: `
<el-table :data="testData" row-key="release">
<el-table-column prop="name" label="片名" />
<el-table-column prop="release" label="发行日期" />
<el-table-column prop="director" label="导演" />
<el-table-column prop="runtime" label="时长(分)" />
</el-table>
`,
data() {
const testData = getTestData();
testData[1].children = [
{
name: 'A Bug\'s Life copy 1', release: '1998-11-25-1', director: 'John Lasseter', runtime: 95
},
{
name: 'A Bug\'s Life copy 2', release: '1998-11-25-2', director: 'John Lasseter', runtime: 95
}
];
return {
testData: testData
};
}
}, true);
setTimeout(() => {
const rows = vm.$el.querySelectorAll('.el-table__row');
expect(rows.length).to.equal(7);
const childRows = vm.$el.querySelectorAll('.el-table__row--level-1');
expect(childRows.length).to.equal(2);
childRows.forEach(item => {
expect(item.style.display).to.equal('none');
});
vm.$el.querySelector('.el-table__expand-icon').click();
setTimeout(() => {
childRows.forEach(item => {
expect(item.style.display).to.equal('');
});
done();
}, DELAY);
}, DELAY);
});
it('load substree row data', (done) => {
const vm = createVue({
template: `
<el-table :data="testData" row-key="release" lazy :load="load">
<el-table-column prop="name" label="片名" />
<el-table-column prop="release" label="发行日期" />
<el-table-column prop="director" label="导演" />
<el-table-column prop="runtime" label="时长(分)" />
</el-table>
`,
data() {
const testData = getTestData();
testData[1].hasChildren = true;
return {
testData: testData
};
},
methods: {
load(row, treeNode, resolve) {
resolve([
{
name: 'A Bug\'s Life copy 1', release: '1998-11-25-1', director: 'John Lasseter', runtime: 95
},
{
name: 'A Bug\'s Life copy 2', release: '1998-11-25-2', director: 'John Lasseter', runtime: 95
}
]);
}
}
}, true);
setTimeout(() => {
const expandIcon = vm.$el.querySelector('.el-table__expand-icon');
expandIcon.click();
setTimeout(() => {
expect(expandIcon.classList.contains('el-table__expand-icon--expanded')).to.be.true;
expect(vm.$el.querySelectorAll('.el-table__row').length).to.equal(7);
done();
}, DELAY);
}, DELAY);
});
});

17
types/table.d.ts vendored
View File

@ -29,6 +29,14 @@ export interface cellCallbackParams {
columnIndex: number
}
export interface treeNode {
rowKey: string | number,
isLeaf: boolean,
level: number,
expanded: boolean,
loaded: boolean
}
/** Table Component */
export declare class ElTable extends ElementUIComponent {
/** Table data */
@ -58,6 +66,12 @@ export declare class ElTable extends ElementUIComponent {
/** Key of current row, a set only prop */
currentRowKey: string | number
/** Whether to lazy load tree structure data, used with load attribute */
lazy: boolean
/** Horizontal indentation of nodes in adjacent levels in pixels */
indent: number
/** Function that returns custom class names for a row, or a string assigning class names for every row */
rowClassName: string | ((param: rowCallbackParams) => string)
@ -154,4 +168,7 @@ export declare class ElTable extends ElementUIComponent {
/** Sort Table manually */
sort (prop: string, order: string): void
/** method for lazy load subtree data */
load (row: object, treeNode: treeNode, resolve: Function): void
}