mirror of https://github.com/ElemeFE/element
Table: add new features for Table and optimize code (#15709)
parent
99d613fc05
commit
f6bab114e3
|
@ -1332,7 +1332,7 @@ 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.
|
||||
:::demo You can display tree structure data. When row contains the `children` field, it is treated as nested data. For rendering nested data, 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. Both `children` and `hasChildren` can be configured via `tree-props`.
|
||||
|
||||
```html
|
||||
<template>
|
||||
|
@ -1340,11 +1340,12 @@ When the row content is too long and you do not want to display the horizontal s
|
|||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
row-key="id"
|
||||
border
|
||||
row-key="id">
|
||||
default-expand-all>
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="Date"
|
||||
label="date"
|
||||
sortable
|
||||
width="180">
|
||||
</el-table-column>
|
||||
|
@ -1363,7 +1364,7 @@ When the row content is too long and you do not want to display the horizontal s
|
|||
border
|
||||
lazy
|
||||
:load="load"
|
||||
>
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="Date"
|
||||
|
@ -1429,6 +1430,7 @@ When the row content is too long and you do not want to display the horizontal s
|
|||
},
|
||||
methods: {
|
||||
load(tree, treeNode, resolve) {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 31,
|
||||
|
@ -1440,6 +1442,7 @@ When the row content is too long and you do not want to display the horizontal s
|
|||
name: 'wangxiaohu'
|
||||
}
|
||||
])
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1825,7 +1828,7 @@ You can customize row index in `type=index` columns.
|
|||
| 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 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 |
|
||||
| default-expand-all | whether expand all rows by default, works when the table has a column type="expand" or contains tree structure data | 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 | — | |
|
||||
| default-sort | set the default sort column and order. property `prop` is used to set default sort column, property `order` is used to set default sort order | Object | `order`: ascending, descending | if `prop` is set, and `order` is not set, then `order` is default to ascending |
|
||||
| tooltip-effect | tooltip `effect` property | String | dark/light | | dark |
|
||||
|
@ -1836,7 +1839,8 @@ You can customize row index in `type=index` columns.
|
|||
| 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 }) | — | — |
|
||||
| load | method for loading child row data, only works when `lazy` is true | Function(row, treeNode, resolve) | — | — |
|
||||
| tree-props | configuration for rendering nested data| Object | — | { hasChildren: 'hasChildren', children: 'children' } |
|
||||
|
||||
### Table Events
|
||||
| Event Name | Description | Parameters |
|
||||
|
@ -1857,7 +1861,7 @@ You can customize row index in `type=index` columns.
|
|||
| filter-change | column's key. If you need to use the filter-change event, this attribute is mandatory to identify which column is being filtered | filters |
|
||||
| current-change | triggers when current row changes | currentRow, oldCurrentRow |
|
||||
| header-dragend | triggers after changing a column's width by dragging the column header's border | newWidth, oldWidth, column, event |
|
||||
| expand-change | triggers when user expands or collapses a row | row, expandedRows |
|
||||
| expand-change | triggers when user expands or collapses a row (for expandable table, second param is expandedRows; for tree Table, second param is expanded) | row, (expandedRows \| expanded) |
|
||||
|
||||
### Table Methods
|
||||
| Method | Description | Parameters |
|
||||
|
@ -1865,7 +1869,7 @@ You can customize row index in `type=index` columns.
|
|||
| clearSelection | used in multiple selection Table, clear user selection | — |
|
||||
| toggleRowSelection | used in multiple selection Table, toggle if a certain row is selected. With the second parameter, you can directly set if this row is selected | row, selected |
|
||||
| toggleAllSelection | used in multiple selection Table, toggle the selected state of all rows | - |
|
||||
| toggleRowExpansion | used in expandable Table, toggle if a certain row is expanded. With the second parameter, you can directly set if this row is expanded or collapsed | row, expanded |
|
||||
| toggleRowExpansion | used in expandable Table or tree Table, toggle if a certain row is expanded. With the second parameter, you can directly set if this row is expanded or collapsed | row, expanded |
|
||||
| setCurrentRow | used in single selection Table, set a certain row selected. If called without any parameter, it will clear selection. | row |
|
||||
| clearSort | clear sorting, restore data to the original order | — |
|
||||
| clearFilter | clear filters of the columns whose `columnKey` are passed in. If no params, clear all filters | columnKeys |
|
||||
|
|
|
@ -1334,7 +1334,7 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
|
|||
|
||||
### Datos tree y modo lazy
|
||||
|
||||
:::demo Puede mostrar la estructura de datos tipo tree。Cuando se usa, la prop`row-key` es requerida。Entonces, los datos de las filas de los hijos pueden ser cargados asincrónicamente. Poner la propiedad `lazy` de Table a true y la función `load`. Especifique el atributo `hasChildren` en la fila para determinar qué fila contiene hijos.
|
||||
:::demo You can display tree structure data. When row contains the `children` field, it is treated as nested data. For rendering nested data, 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. Both `children` and `hasChildren` can be configured via `tree-props`.
|
||||
|
||||
```html
|
||||
<template>
|
||||
|
@ -1342,8 +1342,9 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
|
|||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
row-key="id"
|
||||
border
|
||||
row-key="id">
|
||||
default-expand-all>
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="日期"
|
||||
|
@ -1365,7 +1366,7 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
|
|||
border
|
||||
lazy
|
||||
:load="load"
|
||||
>
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="date"
|
||||
|
@ -1431,6 +1432,7 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
|
|||
},
|
||||
methods: {
|
||||
load(tree, treeNode, resolve) {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 31,
|
||||
|
@ -1442,6 +1444,7 @@ Cuando el contenido de la fila es demasiado largo y busca no mostrar la barra de
|
|||
name: 'wangxiaohu'
|
||||
}
|
||||
])
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1841,7 +1844,8 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
|
|||
| 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 | indentación horizontal de los datos en formato tree | Number | — | 16 |
|
||||
| lazy | si se realiza un lazy loading de los datos | Boolean | — | — |
|
||||
| load | metodo para cargar las filas de los hijos, solamente funciona cuando `lazy`es true | Function({ row, treeNode, resolve }) | — | — |
|
||||
| load | metodo para cargar las filas de los hijos, solamente funciona cuando `lazy`es true | Function(row, treeNode, resolve) | — | — |
|
||||
| tree-props | configuration for rendering nested data | Object | — | { hasChildren: 'hasChildren', children: 'children' } |
|
||||
|
||||
### Eventos de la tabla
|
||||
| Nombre del evento | Descripción | Parámetros |
|
||||
|
@ -1862,7 +1866,7 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
|
|||
| filter-change | clave de la columna. Si necesitas utilizar el evento filter-change, este atributo es obligatorio para identificar cuál columna está siendo filtrada | filters |
|
||||
| current-change | se dispara cuando la fila actual cambia | currentRow, oldCurrentRow |
|
||||
| header-dragend | se dispara después de modificar el ancho de una columna arrastrando el borde de la cabecera. | newWidth, oldWidth, column, event |
|
||||
| expand-change | se dispara cuando el usuario expande o colapsa una fila | row, expandedRows |
|
||||
| expand-change | triggers when user expands or collapses a row (for expandable table, second param is expandedRows; for tree Table, second param is expanded) | row, (expandedRows \| expanded) |
|
||||
|
||||
### Métodos de la tabla
|
||||
| Metodo | Descripción | Parametros |
|
||||
|
@ -1870,7 +1874,7 @@ Puede personalizar el índice de la fila con la propiedad `type=index` de las co
|
|||
| clearSelection | utilizado en selección múltiple de la tabla, limpiar selección | — |
|
||||
| toggleRowSelection | utilizado en selección múltiple de la tabla, alterna si una cierta fila es seleccionada. Con el segundo parámetro, puede directamente establecer si la fila es seleccionada | row, selected |
|
||||
| toggleAllSelection | usado en Table de seleccion multiple, cambia los estados de seleccion de todas las filas. | - |
|
||||
| toggleRowExpansion | utilizado en tabla expandible, alterna si una cierta fila es expandida. Con el segundo parámetro, puede directamente establecer si esta fila es expandida o colapsada | row, expanded |
|
||||
| toggleRowExpansion | used in expandable Table or tree Table, toggle if a certain row is expanded. With the second parameter, you can directly set if this row is expanded or collapsed | row, expanded |
|
||||
| setCurrentRow | utilizado en tabla con selección sencilla, establece una cierta fila seleccionada. Si es llamado sin ningún parámetro, este puede limpiar la selección | row |
|
||||
| clearSort | limpiar ordenamiento, restaurar datos a orden original | — |
|
||||
| clearFilter | Se utiliza para borrar todas las condiciones de filtro cuando no se pasan parámetros, los datos se restaurarán a un estado sin filtrar, o se puede pasar una matriz de columnas para borrar las condiciones de filtro de la columna especificada. | columnKey |
|
||||
|
|
|
@ -1335,7 +1335,7 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
|
|||
|
||||
### Arborescence et lazy loading
|
||||
|
||||
:::demo Vous pouvez afficher les données en arborescence, la propriété `row-key` devenant dans ce cas obligatoire. Les données enfants peuvent aussi être chargées de manière asynchrone. Mettez la propriété `lazy` à `true` and utilisez la fonction `load`. Spécifiez l'attribut `hasChildren` pour déterminer quelle ligne contient les enfants.
|
||||
:::demo You can display tree structure data. When row contains the `children` field, it is treated as nested data. For rendering nested data, 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. Both `children` and `hasChildren` can be configured via `tree-props`.
|
||||
|
||||
```html
|
||||
<template>
|
||||
|
@ -1343,11 +1343,12 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
|
|||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
row-key="id"
|
||||
border
|
||||
row-key="id">
|
||||
default-expand-all>
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="Date"
|
||||
label="date"
|
||||
sortable
|
||||
width="180">
|
||||
</el-table-column>
|
||||
|
@ -1366,7 +1367,7 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
|
|||
border
|
||||
lazy
|
||||
:load="load"
|
||||
>
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="Date"
|
||||
|
@ -1432,6 +1433,7 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
|
|||
},
|
||||
methods: {
|
||||
load(tree, treeNode, resolve) {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 31,
|
||||
|
@ -1443,6 +1445,7 @@ Lorsque le contenu d'une ligne est trop long et que vous ne souhaitez pas affich
|
|||
name: 'wangxiaohu'
|
||||
}
|
||||
])
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1832,7 +1835,7 @@ Vous pouvez personnaliser les indices des colonnes de type `index`.
|
|||
| 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 | — | — |
|
||||
| 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 |
|
||||
| default-expand-all | whether expand all rows by default, works when the table has a column type="expand" or contains tree structure data | 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 | — | |
|
||||
| default-sort | Détermine l'ordre de tri par défaut. La propriété `prop` détermine la colonne par défaut, `order` détermine l'ordre par défaut. | Object | `order`: ascending, descending | Si `order` est absent, son défaut sera `ascending`. |
|
||||
| tooltip-effect | Propriété `effect` de Tooltip. | String | dark/light | | dark |
|
||||
|
@ -1841,9 +1844,10 @@ 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 | Indentation horizontale de l'arborescence. | Number | — | 16 |
|
||||
| lazy | Si le lazy loading doit être utilisé. | Boolean | — | — |
|
||||
| load | Méthode a utiliser pour le lazy loading, ne fonctionne que lorsque `lazy` est `true`. | Function({ row, treeNode, resolve }) | — | — |
|
||||
| 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 }) | — | — |
|
||||
| tree-props | configuration for rendering nested data | Object | — | { hasChildren: 'hasChildren', children: 'children' } |
|
||||
|
||||
### Évènements de Table
|
||||
|
||||
|
@ -1865,7 +1869,7 @@ Vous pouvez personnaliser les indices des colonnes de type `index`.
|
|||
| filter-change | column's key. If you need to use the filter-change event, this attribute is mandatory to identify which column is being filtered. | filters |
|
||||
| current-change | Se déclenche quand la ligne sélectionnée change. | currentRow, oldCurrentRow |
|
||||
| header-dragend | Se déclenche après un changement de taille de colonne en déplaçant la ligne verticale du header. | newWidth, oldWidth, column, event |
|
||||
| expand-change | Se déclenche quand l'utilisateur étend ou réduit une ligne. | row, expandedRows |
|
||||
| expand-change | triggers when user expands or collapses a row (for expandable table, second param is expandedRows; for tree Table, second param is expanded) | row, (expandedRows \| expanded) |
|
||||
|
||||
### Méthodes de Table
|
||||
|
||||
|
@ -1874,7 +1878,7 @@ Vous pouvez personnaliser les indices des colonnes de type `index`.
|
|||
| clearSelection | Dans les tables avec sélection multiple, efface la sélection. | — |
|
||||
| toggleRowSelection | Dans les tables avec sélection multiple, change la sélection d'une ligne. Grâce au deuxième paramètre vous pouvez directement décider si cette ligne est sélectionnée. | row, selected |
|
||||
| toggleAllSelection | Utilisé dans les tables à sélection multiples, sélectionne toutes les lignes. | - |
|
||||
| toggleRowExpansion | Pour les lignes extensibles, change l'état de la ligne. Grâce au deuxième paramètre vous pouvez directement décider si cette ligne est étendue. | row, expanded |
|
||||
| toggleRowExpansion | used in expandable Table or tree Table, toggle if a certain row is expanded. With the second parameter, you can directly set if this row is expanded or collapsed | row, expanded |
|
||||
| setCurrentRow | Dans les tables à sélection simple, sélectionne une ligne. Sans paramètre la sélection est effacé. | row |
|
||||
| clearSort | Efface le tri. | — |
|
||||
| clearFilter | Efface les filtres des colonnes dont les `columnKey` sont passées. Si aucun paramètre, efface tout les filtres. | columnKeys |
|
||||
|
|
|
@ -1277,7 +1277,7 @@
|
|||
|
||||
### 树形数据与懒加载
|
||||
|
||||
:::demo 支持树类型的数据。此时,必须要指定 `row-key`。支持子节点数据异步加载。设置 Table 的 `lazy` 属性为 true 与 加载函数 `load` ,指定 row 中的 `hasChildren` 来确定哪些行是包含子节点。
|
||||
:::demo 支持树类型的数据的显示。当 row 中包含 `children` 字段时,被视为树形数据。渲染树形数据时,必须要指定 `row-key`。支持子节点数据异步加载。设置 Table 的 `lazy` 属性为 true 与加载函数 `load` 。通过指定 row 中的 `hasChildren` 字段来指定哪些行是包含子节点。`children` 与 `hasChildren` 都可以通过 `tree-props` 配置。
|
||||
|
||||
```html
|
||||
<template>
|
||||
|
@ -1285,8 +1285,10 @@
|
|||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
row-key="id"
|
||||
border
|
||||
row-key="id">
|
||||
default-expand-all
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="日期"
|
||||
|
@ -1312,7 +1314,7 @@
|
|||
border
|
||||
lazy
|
||||
:load="load"
|
||||
>
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="日期"
|
||||
|
@ -1392,6 +1394,7 @@
|
|||
},
|
||||
methods: {
|
||||
load(tree, treeNode, resolve) {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 31,
|
||||
|
@ -1405,6 +1408,7 @@
|
|||
address: '上海市普陀区金沙江路 1519 弄'
|
||||
}
|
||||
])
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1867,7 +1871,7 @@
|
|||
| 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 | — | — |
|
||||
| empty-text | 空数据时显示的文本内容,也可以通过 `slot="empty"` 设置 | String | — | 暂无数据 |
|
||||
| default-expand-all | 是否默认展开所有行,当 Table 中存在 type="expand" 的 Column 的时候有效 | Boolean | — | false |
|
||||
| default-expand-all | 是否默认展开所有行,当 Tale 包含展开行存在或者为树形表格时有效 | Boolean | — | false |
|
||||
| expand-row-keys | 可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。| Array | — | |
|
||||
| default-sort | 默认的排序列的 prop 和顺序。它的`prop`属性指定默认的排序的列,`order`指定默认排序的顺序| Object | `order`: ascending, descending | 如果只指定了`prop`, 没有指定`order`, 则默认顺序是ascending |
|
||||
| tooltip-effect | tooltip `effect` 属性 | String | dark/light | | dark |
|
||||
|
@ -1878,7 +1882,8 @@
|
|||
| select-on-indeterminate | 在多选表格中,当仅有部分行被选中时,点击表头的多选框时的行为。若为 true,则选中所有行;若为 false,则取消选择所有行 | Boolean | — | true |
|
||||
| indent | 展示树形数据时,树节点的缩进 | Number | — | 16 |
|
||||
| lazy | 是否懒加载子节点数据 | Boolean | — | — |
|
||||
| load | 加载子节点数据的函数,lazy 为 true 时生效 | Function({ row, treeNode, resolve }) | — | — |
|
||||
| load | 加载子节点数据的函数,lazy 为 true 时生效,函数第二个参数包含了节点的层级信息 | Function(row, treeNode, resolve) | — | — |
|
||||
| tree-props | 渲染嵌套数据的配置选项 | Object | — | { hasChildren: 'hasChildren', children: 'children' } |
|
||||
|
||||
### Table Events
|
||||
| 事件名 | 说明 | 参数 |
|
||||
|
@ -1899,7 +1904,7 @@
|
|||
| filter-change | 当表格的筛选条件发生变化的时候会触发该事件,参数的值是一个对象,对象的 key 是 column 的 columnKey,对应的 value 为用户选择的筛选条件的数组。 | filters |
|
||||
| current-change | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight-current-row 属性 | currentRow, oldCurrentRow |
|
||||
| header-dragend | 当拖动表头改变了列的宽度的时候会触发该事件 | newWidth, oldWidth, column, event |
|
||||
| expand-change | 当用户对某一行展开或者关闭的时候会触发该事件 | row, expandedRows |
|
||||
| expand-change | 当用户对某一行展开或者关闭的时候会触发该事件(展开行时,回调的第二个参数为 expandedRows;树形表格时第二参数为 expanded) | row, (expandedRows \| expanded) |
|
||||
|
||||
### Table Methods
|
||||
| 方法名 | 说明 | 参数 |
|
||||
|
@ -1907,7 +1912,7 @@
|
|||
| clearSelection | 用于多选表格,清空用户的选择 | — |
|
||||
| toggleRowSelection | 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中) | row, selected |
|
||||
| toggleAllSelection | 用于多选表格,切换所有行的选中状态 | - |
|
||||
| toggleRowExpansion | 用于可展开表格,切换某一行的展开状态,如果使用了第二个参数,则是设置这一行展开与否(expanded 为 true 则展开) | row, expanded |
|
||||
| toggleRowExpansion | 用于可展开表格与树形表格,切换某一行的展开状态,如果使用了第二个参数,则是设置这一行展开与否(expanded 为 true 则展开) | row, expanded |
|
||||
| setCurrentRow | 用于单选表格,设定某一行为选中行,如果调用时不加参数,则会取消目前高亮行的选中状态。 | row |
|
||||
| clearSort | 用于清空排序条件,数据会恢复成未排序的状态 | — |
|
||||
| clearFilter | 不传入参数时用于清空所有过滤条件,数据会恢复成未过滤的状态,也可传入由columnKey组成的数组以清除指定列的过滤条件 | columnKey |
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import { getPropByPath } from 'element-ui/src/utils/util';
|
||||
|
||||
export const cellStarts = {
|
||||
default: {
|
||||
order: ''
|
||||
},
|
||||
selection: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: '',
|
||||
className: 'el-table-column--selection'
|
||||
},
|
||||
expand: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: ''
|
||||
},
|
||||
index: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: ''
|
||||
}
|
||||
};
|
||||
|
||||
// 这些选项不应该被覆盖
|
||||
export const cellForced = {
|
||||
selection: {
|
||||
renderHeader: function(h, { store }) {
|
||||
return <el-checkbox
|
||||
disabled={ store.states.data && store.states.data.length === 0 }
|
||||
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
|
||||
nativeOn-click={ this.toggleAllSelection }
|
||||
value={ this.isAllSelected } />;
|
||||
},
|
||||
renderCell: function(h, { row, column, store, $index }) {
|
||||
return <el-checkbox
|
||||
nativeOn-click={ (event) => event.stopPropagation() }
|
||||
value={ store.isSelected(row) }
|
||||
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
|
||||
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false
|
||||
},
|
||||
index: {
|
||||
renderHeader: function(h, { column }) {
|
||||
return column.label || '#';
|
||||
},
|
||||
renderCell: function(h, { $index, column }) {
|
||||
let i = $index + 1;
|
||||
const index = column.index;
|
||||
|
||||
if (typeof index === 'number') {
|
||||
i = $index + index;
|
||||
} else if (typeof index === 'function') {
|
||||
i = index($index);
|
||||
}
|
||||
|
||||
return <div>{ i }</div>;
|
||||
},
|
||||
sortable: false
|
||||
},
|
||||
expand: {
|
||||
renderHeader: function(h, { column }) {
|
||||
return column.label || '';
|
||||
},
|
||||
renderCell: function(h, { row, store }) {
|
||||
const classes = ['el-table__expand-icon'];
|
||||
if (store.states.expandRows.indexOf(row) > -1) {
|
||||
classes.push('el-table__expand-icon--expanded');
|
||||
}
|
||||
const callback = function(e) {
|
||||
e.stopPropagation();
|
||||
store.toggleRowExpansion(row);
|
||||
};
|
||||
return (<div class={ classes }
|
||||
on-click={callback}>
|
||||
<i class='el-icon el-icon-arrow-right'></i>
|
||||
</div>);
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
className: 'el-table__expand-column'
|
||||
}
|
||||
};
|
||||
|
||||
export function defaultRenderCell(h, { row, column, $index }) {
|
||||
const property = column.property;
|
||||
const value = property && getPropByPath(row, property).v;
|
||||
if (column && column.formatter) {
|
||||
return column.formatter(row, column, value, $index);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function treeCellPrefix(h, { row, treeNode, store }) {
|
||||
if (!treeNode) return null;
|
||||
const ele = [];
|
||||
const callback = function(e) {
|
||||
e.stopPropagation();
|
||||
store.loadOrToggle(row);
|
||||
};
|
||||
if (treeNode.indent) {
|
||||
ele.push(<span class="el-table__indent" style={{'padding-left': treeNode.indent + 'px'}}></span>);
|
||||
}
|
||||
if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) {
|
||||
const expandClasses = ['el-table__expand-icon', treeNode.expanded ? 'el-table__expand-icon--expanded' : ''];
|
||||
let iconClasses = ['el-icon-arrow-right'];
|
||||
if (treeNode.loading) {
|
||||
iconClasses = ['el-icon-loading'];
|
||||
}
|
||||
ele.push(<div class={ expandClasses }
|
||||
on-click={ callback }>
|
||||
<i class={ iconClasses }></i>
|
||||
</div>);
|
||||
} else {
|
||||
ele.push(<span class="el-table__placeholder"></span>);
|
||||
}
|
||||
return ele;
|
||||
}
|
|
@ -72,17 +72,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
customRender(h) {
|
||||
return (<div class="el-table-filter">
|
||||
<div class="el-table-filter__content">
|
||||
</div>
|
||||
<div class="el-table-filter__bottom">
|
||||
<button on-click={ this.handleConfirm }>{ this.t('el.table.confirmFilter') }</button>
|
||||
<button on-click={ this.handleReset }>{ this.t('el.table.resetFilter') }</button>
|
||||
</div>
|
||||
</div>);
|
||||
},
|
||||
|
||||
methods: {
|
||||
isActive(filter) {
|
||||
return filter.value === this.filterValue;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { arrayFind } from 'element-ui/src/utils/util';
|
||||
import { getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
current: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCurrentRowKey(key) {
|
||||
this.assertRowKey();
|
||||
|
||||
const { states } = this;
|
||||
const { data = [], rowKey } = states;
|
||||
const currentRow = arrayFind(data, item => getRowIdentity(item, rowKey) === key);
|
||||
states.currentRow = currentRow ? currentRow : null;
|
||||
},
|
||||
|
||||
updateCurrentRow() {
|
||||
const { states, table } = this;
|
||||
const { rowKey } = states;
|
||||
// data 为 null 时,结构时的默认值会被忽略
|
||||
const data = states.data || [];
|
||||
const oldCurrentRow = states.currentRow;
|
||||
|
||||
// 当 currentRow 不在 data 中时尝试更新数据
|
||||
if (data.indexOf(oldCurrentRow) === -1 && oldCurrentRow) {
|
||||
let newCurrentRow = null;
|
||||
if (rowKey) {
|
||||
newCurrentRow = arrayFind(data, item => {
|
||||
return getRowIdentity(item, rowKey) === getRowIdentity(oldCurrentRow, rowKey);
|
||||
});
|
||||
}
|
||||
states.currentRow = newCurrentRow;
|
||||
if (newCurrentRow !== oldCurrentRow) {
|
||||
table.$emit('current-change', null, oldCurrentRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
import { toggleRowStatus, getKeysMap, getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
defaultExpandAll: false,
|
||||
expandRows: []
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateExpandRows() {
|
||||
const { data = [], rowKey, defaultExpandAll, expandRows } = this.states;
|
||||
if (defaultExpandAll) {
|
||||
this.states.expandRows = data.slice();
|
||||
} else if (rowKey) {
|
||||
// TODO:这里的代码可以优化
|
||||
const expandRowsMap = getKeysMap(expandRows, rowKey);
|
||||
this.states.expandRows = data.reduce((prev, row) => {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
const rowInfo = expandRowsMap[rowId];
|
||||
if (rowInfo) {
|
||||
prev.push(row);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
} else {
|
||||
this.states.expandRows = [];
|
||||
}
|
||||
},
|
||||
|
||||
toggleRowExpansion(row, expanded) {
|
||||
const changed = toggleRowStatus(this.states.expandRows, row, expanded);
|
||||
if (changed) {
|
||||
this.table.$emit('expand-change', row, this.states.expandRows.slice());
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
setExpandRowKeys(rowKeys) {
|
||||
this.assertRowKey();
|
||||
// TODO:这里的代码可以优化
|
||||
const { data, rowKey } = this.states;
|
||||
const keysMap = getKeysMap(data, rowKey);
|
||||
this.states.expandRows = rowKeys.reduce((prev, cur) => {
|
||||
const info = keysMap[cur];
|
||||
if (info) {
|
||||
prev.push(info.row);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
},
|
||||
|
||||
isRowExpanded(row) {
|
||||
const { expandRows = [], rowKey } = this.states;
|
||||
if (rowKey) {
|
||||
const expandMap = getKeysMap(expandRows, rowKey);
|
||||
return !!expandMap[getRowIdentity(row, rowKey)];
|
||||
}
|
||||
return expandRows.indexOf(row) !== -1;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import Store from './index';
|
||||
|
||||
export function createStore(table, initialState = {}) {
|
||||
if (!table) {
|
||||
throw new Error('Table is required.');
|
||||
}
|
||||
|
||||
const store = new Store();
|
||||
store.table = table;
|
||||
Object.keys(initialState).forEach(key => {
|
||||
store.states[key] = initialState[key];
|
||||
});
|
||||
return store;
|
||||
}
|
||||
|
||||
export function mapStates(mapper) {
|
||||
const res = {};
|
||||
Object.keys(mapper).forEach(key => {
|
||||
const value = mapper[key];
|
||||
let fn;
|
||||
if (typeof value === 'string') {
|
||||
fn = function() {
|
||||
return this.store.states[value];
|
||||
};
|
||||
} else if (typeof value === 'function') {
|
||||
fn = function() {
|
||||
return value.call(this, this.store.states);
|
||||
};
|
||||
} else {
|
||||
console.error('invalid value type');
|
||||
}
|
||||
if (fn) {
|
||||
res[key] = fn;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
|
@ -0,0 +1,155 @@
|
|||
import Vue from 'vue';
|
||||
import Watcher from './watcher';
|
||||
import { arrayFind } from 'element-ui/src/utils/util';
|
||||
|
||||
Watcher.prototype.mutations = {
|
||||
setData(states, data) {
|
||||
const dataInstanceChanged = states._data !== data;
|
||||
states._data = data;
|
||||
|
||||
this.execQuery();
|
||||
// 数据变化,更新部分数据。
|
||||
// 没有使用 computed,而是手动更新部分数据 https://github.com/vuejs/vue/issues/6660#issuecomment-331417140
|
||||
this.updateCurrentRow();
|
||||
this.updateExpandRows();
|
||||
if (!states.reserveSelection) {
|
||||
if (dataInstanceChanged) {
|
||||
this.clearSelection();
|
||||
} else {
|
||||
this.cleanSelection();
|
||||
}
|
||||
} else {
|
||||
this.assertRowKey();
|
||||
this.updateSelectionByRowKey();
|
||||
}
|
||||
this.updateAllSelected();
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
insertColumn(states, column, index, parent) {
|
||||
let array = states._columns;
|
||||
if (parent) {
|
||||
array = parent.children;
|
||||
if (!array) array = parent.children = [];
|
||||
}
|
||||
|
||||
if (typeof index !== 'undefined') {
|
||||
array.splice(index, 0, column);
|
||||
} else {
|
||||
array.push(column);
|
||||
}
|
||||
|
||||
if (column.type === 'selection') {
|
||||
states.selectable = column.selectable;
|
||||
states.reserveSelection = column.reserveSelection;
|
||||
}
|
||||
|
||||
if (this.table.$ready) {
|
||||
this.updateColumns(); // hack for dynamics insert column
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
removeColumn(states, column, parent) {
|
||||
let array = states._columns;
|
||||
if (parent) {
|
||||
array = parent.children;
|
||||
if (!array) array = parent.children = [];
|
||||
}
|
||||
if (array) {
|
||||
array.splice(array.indexOf(column), 1);
|
||||
}
|
||||
|
||||
if (this.table.$ready) {
|
||||
this.updateColumns(); // hack for dynamics remove column
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
sort(states, options) {
|
||||
const { prop, order } = options;
|
||||
if (prop) {
|
||||
// TODO:nextTick 是否有必要?
|
||||
Vue.nextTick(() => {
|
||||
const column = arrayFind(states.columns, column => column.property === prop);
|
||||
if (column) {
|
||||
column.order = order;
|
||||
this.updateSort(column, prop, order);
|
||||
this.commit('changeSortCondition');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
changeSortCondition(states, options) {
|
||||
// 修复 pr https://github.com/ElemeFE/element/pull/15012 导致的 bug
|
||||
const { sortingColumn: column, sortProp: prop, sortOrder: order } = states;
|
||||
if (order === null) {
|
||||
states.sortingColumn = null;
|
||||
states.sortProp = null;
|
||||
}
|
||||
const ingore = { filter: true };
|
||||
this.execQuery(ingore);
|
||||
|
||||
if (!options || !options.silent) {
|
||||
this.table.$emit('sort-change', {
|
||||
column,
|
||||
prop,
|
||||
order
|
||||
});
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
filterChange(states, options) {
|
||||
let { column, values, silent } = options;
|
||||
const newFilters = this.updateFilters(column, values);
|
||||
|
||||
this.execQuery();
|
||||
|
||||
if (!silent) {
|
||||
this.table.$emit('filter-change', newFilters);
|
||||
}
|
||||
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
toggleAllSelection() {
|
||||
this.toggleAllSelection();
|
||||
},
|
||||
|
||||
rowSelectedChanged(states, row) {
|
||||
this.toggleRowSelection(row);
|
||||
this.updateAllSelected();
|
||||
},
|
||||
|
||||
setHoverRow(states, row) {
|
||||
states.hoverRow = row;
|
||||
},
|
||||
|
||||
setCurrentRow(states, row) {
|
||||
const oldCurrentRow = states.currentRow;
|
||||
states.currentRow = row;
|
||||
|
||||
if (oldCurrentRow !== row) {
|
||||
this.table.$emit('current-change', row, oldCurrentRow);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Watcher.prototype.commit = function(name, ...args) {
|
||||
const mutations = this.mutations;
|
||||
if (mutations[name]) {
|
||||
mutations[name].apply(this, [this.states].concat(args));
|
||||
} else {
|
||||
throw new Error(`Action not found: ${name}`);
|
||||
}
|
||||
};
|
||||
|
||||
Watcher.prototype.updateTableScrollY = function() {
|
||||
Vue.nextTick(this.table.updateScrollY);
|
||||
};
|
||||
|
||||
export default Watcher;
|
|
@ -0,0 +1,193 @@
|
|||
import { walkTreeNode, getRowIdentity } from '../util';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
// defaultExpandAll 存在于 expand.js 中,这里不重复添加
|
||||
// TODO: 拆分为独立的 TreeTale,在 expand 中,展开行的记录是放在 expandRows 中,统一用法
|
||||
expandRowKeys: [],
|
||||
treeData: {},
|
||||
indent: 16,
|
||||
lazy: false,
|
||||
lazyTreeNodeMap: {},
|
||||
lazyColumnIdentifier: 'hasChildren',
|
||||
childrenColumnName: 'children'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 嵌入型的数据,watch 无法是检测到变化 https://github.com/ElemeFE/element/issues/14998
|
||||
// TODO: 使用 computed 解决该问题,是否会造成性能问题?
|
||||
// @return { id: { level, children } }
|
||||
normalizedData() {
|
||||
if (!this.states.rowKey) return {};
|
||||
const data = this.states.data || [];
|
||||
return this.normalize(data);
|
||||
},
|
||||
// @return { id: { children } }
|
||||
// 针对懒加载的情形,不处理嵌套数据
|
||||
normalizedLazyNode() {
|
||||
const { rowKey, lazyTreeNodeMap, lazyColumnIdentifier } = this.states;
|
||||
const keys = Object.keys(lazyTreeNodeMap);
|
||||
const res = {};
|
||||
if (!keys.length) return res;
|
||||
keys.forEach(key => {
|
||||
if (lazyTreeNodeMap[key].length) {
|
||||
const item = { children: [] };
|
||||
lazyTreeNodeMap[key].forEach(row => {
|
||||
const currentRowKey = getRowIdentity(row, rowKey);
|
||||
item.children.push(currentRowKey);
|
||||
if (row[lazyColumnIdentifier] && !res[currentRowKey]) {
|
||||
res[currentRowKey] = { children: [] };
|
||||
}
|
||||
});
|
||||
res[key] = item;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
normalizedData: 'updateTreeData',
|
||||
// expandRowKeys 在 TreeTable 中也有使用
|
||||
expandRowKeys: 'updateTreeData',
|
||||
normalizedLazyNode: 'updateTreeData'
|
||||
},
|
||||
|
||||
methods: {
|
||||
normalize(data) {
|
||||
const { childrenColumnName, lazyColumnIdentifier, rowKey, lazy } = this.states;
|
||||
const res = {};
|
||||
walkTreeNode(data, (parent, children, level) => {
|
||||
const parentId = getRowIdentity(parent, rowKey);
|
||||
if (Array.isArray(children)) {
|
||||
res[parentId] = {
|
||||
children: children.map(row => getRowIdentity(row, rowKey)),
|
||||
level
|
||||
};
|
||||
} else if (lazy) {
|
||||
// 当 children 不存在且 lazy 为 true,该节点即为懒加载的节点
|
||||
res[parentId] = {
|
||||
children: [],
|
||||
lazy: true,
|
||||
level
|
||||
};
|
||||
}
|
||||
}, childrenColumnName, lazyColumnIdentifier);
|
||||
return res;
|
||||
},
|
||||
|
||||
updateTreeData() {
|
||||
const nested = this.normalizedData;
|
||||
const normalizedLazyNode = this.normalizedLazyNode;
|
||||
const keys = Object.keys(nested);
|
||||
if (!keys.length) return;
|
||||
const { treeData: oldTreeData, defaultExpandAll, expandRowKeys, lazy } = this.states;
|
||||
const newTreeData = {};
|
||||
const rootLazyRowKeys = [];
|
||||
const getExpanded = (oldValue, key) => {
|
||||
const included = defaultExpandAll || (expandRowKeys && expandRowKeys.indexOf(key) !== -1);
|
||||
return !!((oldValue && oldValue.expanded) || included);
|
||||
};
|
||||
// 合并 expanded 与 display,确保数据刷新后,状态不变
|
||||
keys.forEach(key => {
|
||||
const oldValue = oldTreeData[key];
|
||||
const newValue = { ...nested[key] };
|
||||
newValue.expanded = getExpanded(oldValue, key);
|
||||
if (newValue.lazy) {
|
||||
const { loaded = false, loading = false } = oldValue || {};
|
||||
newValue.loaded = !!loaded;
|
||||
newValue.loading = !!loading;
|
||||
rootLazyRowKeys.push(key);
|
||||
}
|
||||
newTreeData[key] = newValue;
|
||||
});
|
||||
// 根据懒加载数据更新 treeData
|
||||
const lazyKeys = Object.keys(normalizedLazyNode);
|
||||
if (lazy && lazyKeys.length && rootLazyRowKeys.length) {
|
||||
lazyKeys.forEach(key => {
|
||||
const oldValue = oldTreeData[key];
|
||||
const lazyNodeChildren = normalizedLazyNode[key].children;
|
||||
if (rootLazyRowKeys.indexOf(key) !== -1) {
|
||||
// 懒加载的 root 节点,更新一下原有的数据,原来的 children 一定是空数组
|
||||
if (newTreeData[key].children.length !== 0) {
|
||||
throw new Error('[ElTable]children must be an empty array.');
|
||||
}
|
||||
newTreeData[key].children = lazyNodeChildren;
|
||||
} else {
|
||||
const { loaded = false, loading = false } = oldValue || {};
|
||||
newTreeData[key] = {
|
||||
loaded: !!loaded,
|
||||
loading: !!loading,
|
||||
expanded: getExpanded(oldValue, key),
|
||||
children: lazyNodeChildren,
|
||||
level: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
this.states.treeData = newTreeData;
|
||||
this.updateTableScrollY();
|
||||
},
|
||||
|
||||
updateTreeExpandKeys(value) {
|
||||
// 仅仅在包含嵌套数据时才去更新
|
||||
if (Object.keys(this.normalizedData).length) {
|
||||
this.states.expandRowKeys = value;
|
||||
this.updateTreeData();
|
||||
}
|
||||
},
|
||||
|
||||
toggleTreeExpansion(row, expanded) {
|
||||
this.assertRowKey();
|
||||
|
||||
const { rowKey, treeData } = this.states;
|
||||
const id = getRowIdentity(row, rowKey);
|
||||
const data = id && treeData[id];
|
||||
const oldExpanded = treeData[id].expanded;
|
||||
if (id && data && ('expanded' in data)) {
|
||||
expanded = typeof expanded === 'undefined' ? !data.expanded : expanded;
|
||||
treeData[id].expanded = expanded;
|
||||
if (oldExpanded !== expanded) {
|
||||
this.table.$emit('expand-change', row, expanded);
|
||||
}
|
||||
this.updateTableScrollY();
|
||||
}
|
||||
},
|
||||
|
||||
loadOrToggle(row) {
|
||||
this.assertRowKey();
|
||||
const { lazy, treeData, rowKey } = this.states;
|
||||
const id = getRowIdentity(row, rowKey);
|
||||
const data = treeData[id];
|
||||
if (lazy && data && ('loaded' in data) && !data.loaded) {
|
||||
this.loadData(row, id, data);
|
||||
} else {
|
||||
this.toggleTreeExpansion(row);
|
||||
}
|
||||
},
|
||||
|
||||
loadData(row, key, treeNode) {
|
||||
const { load } = this.table;
|
||||
const { lazyTreeNodeMap, treeData } = this.states;
|
||||
if (load && !treeData[key].loaded) {
|
||||
treeData[key].loading = true;
|
||||
load(row, treeNode, (data) => {
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('[ElTable] data must be an array');
|
||||
}
|
||||
treeData[key].loading = false;
|
||||
treeData[key].loaded = true;
|
||||
treeData[key].expanded = true;
|
||||
if (data.length) {
|
||||
this.$set(lazyTreeNodeMap, key, data);
|
||||
}
|
||||
this.table.$emit('expand-change', row, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,388 @@
|
|||
import Vue from 'vue';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import merge from 'element-ui/src/utils/merge';
|
||||
import { getKeysMap, getRowIdentity, getColumnById, getColumnByKey, orderBy, toggleRowStatus } from '../util';
|
||||
import expand from './expand';
|
||||
import current from './current';
|
||||
import tree from './tree';
|
||||
|
||||
const sortData = (data, states) => {
|
||||
const sortingColumn = states.sortingColumn;
|
||||
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
|
||||
return data;
|
||||
}
|
||||
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
|
||||
};
|
||||
|
||||
const doFlattenColumns = (columns) => {
|
||||
const result = [];
|
||||
columns.forEach((column) => {
|
||||
if (column.children) {
|
||||
result.push.apply(result, doFlattenColumns(column.children));
|
||||
} else {
|
||||
result.push(column);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
states: {
|
||||
// 3.0 版本后要求必须设置该属性
|
||||
rowKey: null,
|
||||
|
||||
// 渲染的数据来源,是对 table 中的 data 过滤排序后的结果
|
||||
data: [],
|
||||
|
||||
// 是否包含固定列
|
||||
isComplex: false,
|
||||
|
||||
// 列
|
||||
_columns: [], // 不可响应的
|
||||
originColumns: [],
|
||||
columns: [],
|
||||
fixedColumns: [],
|
||||
rightFixedColumns: [],
|
||||
leafColumns: [],
|
||||
fixedLeafColumns: [],
|
||||
rightFixedLeafColumns: [],
|
||||
leafColumnsLength: 0,
|
||||
fixedLeafColumnsLength: 0,
|
||||
rightFixedLeafColumnsLength: 0,
|
||||
|
||||
// 选择
|
||||
isAllSelected: false,
|
||||
selection: [],
|
||||
reserveSelection: false,
|
||||
selectOnIndeterminate: false,
|
||||
selectable: null,
|
||||
|
||||
// 过滤
|
||||
filters: {}, // 不可响应的
|
||||
filteredData: null,
|
||||
|
||||
// 排序
|
||||
sortingColumn: null,
|
||||
sortProp: null,
|
||||
sortOrder: null,
|
||||
|
||||
hoverRow: null,
|
||||
currentRow: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mixins: [expand, current, tree],
|
||||
|
||||
methods: {
|
||||
// 检查 rowKey 是否存在
|
||||
assertRowKey() {
|
||||
const rowKey = this.states.rowKey;
|
||||
if (!rowKey) throw new Error('[ElTable] prop row-key is required');
|
||||
},
|
||||
|
||||
// 更新列
|
||||
updateColumns() {
|
||||
const states = this.states;
|
||||
const _columns = states._columns || [];
|
||||
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
|
||||
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
|
||||
|
||||
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
|
||||
_columns[0].fixed = true;
|
||||
states.fixedColumns.unshift(_columns[0]);
|
||||
}
|
||||
|
||||
const notFixedColumns = _columns.filter(column => !column.fixed);
|
||||
states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
|
||||
|
||||
const leafColumns = doFlattenColumns(notFixedColumns);
|
||||
const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
|
||||
const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
|
||||
|
||||
states.leafColumnsLength = leafColumns.length;
|
||||
states.fixedLeafColumnsLength = fixedLeafColumns.length;
|
||||
states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
|
||||
|
||||
states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
|
||||
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
|
||||
},
|
||||
|
||||
// 更新 DOM
|
||||
scheduleLayout(needUpdateColumns) {
|
||||
if (needUpdateColumns) {
|
||||
this.updateColumns();
|
||||
}
|
||||
this.table.debouncedUpdateLayout();
|
||||
},
|
||||
|
||||
// 选择
|
||||
isSelected(row) {
|
||||
const { selection = [] } = this.states;
|
||||
return selection.indexOf(row) > -1;
|
||||
},
|
||||
|
||||
clearSelection() {
|
||||
const states = this.states;
|
||||
states.isAllSelected = false;
|
||||
const oldSelection = states.selection;
|
||||
if (states.selection.length) {
|
||||
states.selection = [];
|
||||
}
|
||||
if (oldSelection.length > 0) {
|
||||
this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
|
||||
}
|
||||
},
|
||||
|
||||
cleanSelection() {
|
||||
const selection = this.states.selection || [];
|
||||
const data = this.states.data;
|
||||
const rowKey = this.states.rowKey;
|
||||
let deleted;
|
||||
if (rowKey) {
|
||||
deleted = [];
|
||||
const selectedMap = getKeysMap(selection, rowKey);
|
||||
const dataMap = getKeysMap(data, rowKey);
|
||||
for (let key in selectedMap) {
|
||||
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
|
||||
deleted.push(selectedMap[key].row);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleted = selection.filter((item) => {
|
||||
return data.indexOf(item) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
deleted.forEach((deletedItem) => {
|
||||
selection.splice(selection.indexOf(deletedItem), 1);
|
||||
});
|
||||
|
||||
if (deleted.length) {
|
||||
this.table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
}
|
||||
},
|
||||
|
||||
toggleRowSelection(row, selected) {
|
||||
const changed = toggleRowStatus(this.states.selection, row, selected);
|
||||
if (changed) {
|
||||
const newSelection = this.states.selection ? this.states.selection.slice() : [];
|
||||
this.table.$emit('select', newSelection, row);
|
||||
this.table.$emit('selection-change', newSelection);
|
||||
}
|
||||
},
|
||||
|
||||
toggleAllSelection: debounce(10, function() {
|
||||
const states = this.states;
|
||||
const { data = [], selection } = states;
|
||||
// when only some rows are selected (but not all), select or deselect all of them
|
||||
// depending on the value of selectOnIndeterminate
|
||||
const value = states.selectOnIndeterminate
|
||||
? !states.isAllSelected
|
||||
: !(states.isAllSelected || selection.length);
|
||||
states.isAllSelected = value;
|
||||
|
||||
let selectionChanged = false;
|
||||
data.forEach((row, index) => {
|
||||
if (states.selectable) {
|
||||
if (states.selectable.call(null, row, index) && toggleRowStatus(selection, row, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
} else {
|
||||
if (toggleRowStatus(selection, row, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (selectionChanged) {
|
||||
this.table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
}
|
||||
this.table.$emit('select-all', selection);
|
||||
}),
|
||||
|
||||
updateSelectionByRowKey() {
|
||||
const states = this.states;
|
||||
const { selection, rowKey, data = [] } = states;
|
||||
const selectedMap = getKeysMap(selection, rowKey);
|
||||
// TODO:这里的代码可以优化
|
||||
states.selection = data.reduce((prev, row) => {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
const rowInfo = selectedMap[rowId];
|
||||
if (rowInfo) {
|
||||
prev.push(row);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
},
|
||||
|
||||
updateAllSelected() {
|
||||
const states = this.states;
|
||||
const { selection, rowKey, selectable } = states;
|
||||
// data 为 null 时,结构时的默认值会被忽略
|
||||
const data = states.data || [];
|
||||
if (data.length === 0) {
|
||||
states.isAllSelected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedMap;
|
||||
if (rowKey) {
|
||||
selectedMap = getKeysMap(selection, rowKey);
|
||||
}
|
||||
const isSelected = function(row) {
|
||||
if (selectedMap) {
|
||||
return !!selectedMap[getRowIdentity(row, rowKey)];
|
||||
} else {
|
||||
return selection.indexOf(row) !== -1;
|
||||
}
|
||||
};
|
||||
let isAllSelected = true;
|
||||
let selectedCount = 0;
|
||||
for (let i = 0, j = data.length; i < j; i++) {
|
||||
const item = data[i];
|
||||
const isRowSelectable = selectable && selectable.call(null, item, i);
|
||||
if (!isSelected(item)) {
|
||||
if (!selectable || isRowSelectable) {
|
||||
isAllSelected = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCount === 0) isAllSelected = false;
|
||||
states.isAllSelected = isAllSelected;
|
||||
},
|
||||
|
||||
// 过滤与排序
|
||||
updateFilters(columns, values) {
|
||||
if (!Array.isArray(columns)) {
|
||||
columns = [columns];
|
||||
}
|
||||
const states = this.states;
|
||||
const filters = {};
|
||||
columns.forEach(col => {
|
||||
states.filters[col.id] = values;
|
||||
filters[col.columnKey || col.id] = values;
|
||||
});
|
||||
|
||||
return filters;
|
||||
},
|
||||
|
||||
updateSort(column, prop, order) {
|
||||
this.states.sortingColumn = column;
|
||||
this.states.sortProp = prop;
|
||||
this.states.sortOrder = order;
|
||||
},
|
||||
|
||||
execFilter() {
|
||||
const states = this.states;
|
||||
const { _data, filters } = states;
|
||||
let data = _data;
|
||||
|
||||
Object.keys(filters).forEach((columnId) => {
|
||||
const values = states.filters[columnId];
|
||||
if (!values || values.length === 0) return;
|
||||
const column = getColumnById(this.states, columnId);
|
||||
if (column && column.filterMethod) {
|
||||
data = data.filter((row) => {
|
||||
return values.some(value => column.filterMethod.call(null, value, row, column));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
states.filteredData = data;
|
||||
// states.data = data;
|
||||
},
|
||||
|
||||
execSort() {
|
||||
const states = this.states;
|
||||
states.data = sortData(states.filteredData, states);
|
||||
},
|
||||
|
||||
// 根据 filters 与 sort 去过滤 data
|
||||
execQuery(ignore) {
|
||||
if (!(ignore && ignore.filter)) {
|
||||
this.execFilter();
|
||||
}
|
||||
this.execSort();
|
||||
},
|
||||
|
||||
clearFilter(columnKeys) {
|
||||
const states = this.states;
|
||||
const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
|
||||
|
||||
let panels = {};
|
||||
if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
|
||||
if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
|
||||
if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
|
||||
|
||||
const keys = Object.keys(panels);
|
||||
if (!keys.length) return;
|
||||
|
||||
if (typeof columnKeys === 'string') {
|
||||
columnKeys = [columnKeys];
|
||||
}
|
||||
|
||||
if (Array.isArray(columnKeys)) {
|
||||
const columns = columnKeys.map(key => getColumnByKey(states, key));
|
||||
keys.forEach(key => {
|
||||
const column = columns.find(col => col.id === key);
|
||||
if (column) {
|
||||
// TODO: 优化这里的代码
|
||||
panels[key].filteredValue = [];
|
||||
}
|
||||
});
|
||||
this.commit('filterChange', {
|
||||
column: columns,
|
||||
values: [],
|
||||
silent: true,
|
||||
multi: true
|
||||
});
|
||||
} else {
|
||||
keys.forEach(key => {
|
||||
// TODO: 优化这里的代码
|
||||
panels[key].filteredValue = [];
|
||||
});
|
||||
|
||||
states.filters = {};
|
||||
this.commit('filterChange', {
|
||||
column: {},
|
||||
values: [],
|
||||
silent: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
clearSort() {
|
||||
const states = this.states;
|
||||
if (!states.sortingColumn) return;
|
||||
|
||||
this.updateSort(null, null, null);
|
||||
this.commit('changeSortCondition', {
|
||||
silent: true
|
||||
});
|
||||
},
|
||||
|
||||
// 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用
|
||||
setExpandRowKeysAdapter(val) {
|
||||
// 这里会触发额外的计算,但为了兼容性,暂时这么做
|
||||
this.setExpandRowKeys(val);
|
||||
this.updateTreeExpandKeys(val);
|
||||
},
|
||||
|
||||
// 展开行与 TreeTable 都要使用
|
||||
toggleRowExpansionAdapter(row, expanded) {
|
||||
const hasExpandColumn = this.states.columns.some(({ type }) => type === 'expand');
|
||||
if (hasExpandColumn) {
|
||||
this.toggleRowExpansion(row, expanded);
|
||||
} else {
|
||||
this.toggleTreeExpansion(row, expanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,9 +1,11 @@
|
|||
import { arrayFindIndex } from 'element-ui/src/utils/util';
|
||||
import { getCell, getColumnByCell, getRowIdentity } from './util';
|
||||
import { getStyle, hasClass, removeClass, addClass } from 'element-ui/src/utils/dom';
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
import ElTooltip from 'element-ui/packages/tooltip';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import LayoutObserver from './layout-observer';
|
||||
import { mapStates } from './store/helper';
|
||||
|
||||
export default {
|
||||
name: 'ElTableBody',
|
||||
|
@ -28,30 +30,7 @@ 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 && parent.hasChildren) {
|
||||
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;
|
||||
}, []);
|
||||
}
|
||||
const data = this.data || [];
|
||||
return (
|
||||
<table
|
||||
class="el-table__body"
|
||||
|
@ -60,93 +39,16 @@ export default {
|
|||
border="0">
|
||||
<colgroup>
|
||||
{
|
||||
this._l(this.columns, column => <col name={ column.id } />)
|
||||
this.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
}
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{
|
||||
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);
|
||||
data.reduce((acc, row) => {
|
||||
return acc.concat(this.wrappedRowRender(row, acc.length));
|
||||
}, [])
|
||||
}
|
||||
const tr = (<tr
|
||||
v-show={ treeNode ? treeNode.display : true }
|
||||
style={ this.rowStyle ? this.getRowStyle(row, $index) : null }
|
||||
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={ rowClasses }>
|
||||
{
|
||||
this._l(this.columns, (column, cellIndex) => {
|
||||
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
|
||||
if (!rowspan || !colspan) {
|
||||
return '';
|
||||
} else {
|
||||
const columnData = { ...column };
|
||||
if (colspan !== 1) {
|
||||
columnData.realWidth = columnData.realWidth * colspan;
|
||||
}
|
||||
const data = {
|
||||
store: this.store,
|
||||
_self: this.context || this.table.$vnode.context,
|
||||
column: columnData,
|
||||
row,
|
||||
$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) }
|
||||
class={ this.getCellClass($index, cellIndex, row, column) }
|
||||
rowspan={ rowspan }
|
||||
colspan={ colspan }
|
||||
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
|
||||
on-mouseleave={ this.handleCellMouseLeave }>
|
||||
{
|
||||
column.renderCell.call(
|
||||
this._renderProxy,
|
||||
h,
|
||||
data,
|
||||
columnsHidden[cellIndex]
|
||||
)
|
||||
}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</tr>);
|
||||
if (this.hasExpandColumn && 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>
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
@ -157,53 +59,20 @@ export default {
|
|||
return this.$parent;
|
||||
},
|
||||
|
||||
data() {
|
||||
return this.store.states.data;
|
||||
},
|
||||
|
||||
treeData() {
|
||||
return this.store.states.treeData;
|
||||
},
|
||||
|
||||
columnsCount() {
|
||||
return this.store.states.columns.length;
|
||||
},
|
||||
|
||||
leftFixedLeafCount() {
|
||||
return this.store.states.fixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
rightFixedLeafCount() {
|
||||
return this.store.states.rightFixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
leftFixedCount() {
|
||||
return this.store.states.fixedColumns.length;
|
||||
},
|
||||
|
||||
rightFixedCount() {
|
||||
return this.store.states.rightFixedColumns.length;
|
||||
},
|
||||
|
||||
columns() {
|
||||
return this.store.states.columns;
|
||||
},
|
||||
|
||||
hasExpandColumn() {
|
||||
return this.columns.some(({ type }) => type === 'expand');
|
||||
},
|
||||
...mapStates({
|
||||
data: 'data',
|
||||
columns: 'columns',
|
||||
treeIndent: 'indent',
|
||||
leftFixedLeafCount: 'fixedLeafColumnsLength',
|
||||
rightFixedLeafCount: 'rightFixedLeafColumnsLength',
|
||||
columnsCount: states => states.columns.length,
|
||||
leftFixedCount: states => states.fixedColumns.length,
|
||||
rightFixedCount: states => states.rightFixedColumns.length,
|
||||
hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
|
||||
}),
|
||||
|
||||
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;
|
||||
return arrayFindIndex(this.columns, ({ type }) => type === 'default');
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -211,7 +80,12 @@ export default {
|
|||
// don't trigger getter of currentRow in getCellClass. see https://jsfiddle.net/oe2b4hqt/
|
||||
// update DOM manually. see https://github.com/ElemeFE/element/pull/13954/files#diff-9b450c00d0a9dec0ffad5a3176972e40
|
||||
'store.states.hoverRow'(newVal, oldVal) {
|
||||
if (!this.store.states.isComplex) return;
|
||||
if (!this.store.states.isComplex || this.$isServer) return;
|
||||
let raf = window.requestAnimationFrame;
|
||||
if (!raf) {
|
||||
raf = (fn) => setTimeout(fn, 16);
|
||||
}
|
||||
raf(() => {
|
||||
const rows = this.$el.querySelectorAll('.el-table__row');
|
||||
const oldRow = rows[oldVal];
|
||||
const newRow = rows[newVal];
|
||||
|
@ -221,6 +95,7 @@ export default {
|
|||
if (newRow) {
|
||||
addClass(newRow, 'hover-row');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -256,7 +131,6 @@ export default {
|
|||
getSpan(row, column, rowIndex, columnIndex) {
|
||||
let rowspan = 1;
|
||||
let colspan = 1;
|
||||
|
||||
const fn = this.table.spanMethod;
|
||||
if (typeof fn === 'function') {
|
||||
const result = fn({
|
||||
|
@ -265,7 +139,6 @@ export default {
|
|||
rowIndex,
|
||||
columnIndex
|
||||
});
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
rowspan = result[0];
|
||||
colspan = result[1];
|
||||
|
@ -274,11 +147,7 @@ export default {
|
|||
colspan = result.colspan;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rowspan,
|
||||
colspan
|
||||
};
|
||||
return { rowspan, colspan };
|
||||
},
|
||||
|
||||
getRowStyle(row, rowIndex) {
|
||||
|
@ -289,7 +158,7 @@ export default {
|
|||
rowIndex
|
||||
});
|
||||
}
|
||||
return rowStyle;
|
||||
return rowStyle || null;
|
||||
},
|
||||
|
||||
getRowClass(row, rowIndex) {
|
||||
|
@ -353,6 +222,14 @@ export default {
|
|||
return classes.join(' ');
|
||||
},
|
||||
|
||||
getColspanRealWidth(columns, colspan, index) {
|
||||
if (colspan < 1) {
|
||||
return columns[index].realWidth;
|
||||
}
|
||||
const widthArr = columns.map(({ realWidth }) => realWidth).slice(index, index + colspan);
|
||||
return widthArr.reduce((acc, width) => acc + width, -1);
|
||||
},
|
||||
|
||||
handleCellMouseEnter(event, row) {
|
||||
const table = this.table;
|
||||
const cell = getCell(event);
|
||||
|
@ -401,13 +278,13 @@ export default {
|
|||
this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
|
||||
},
|
||||
|
||||
handleMouseEnter(index) {
|
||||
handleMouseEnter: debounce(30, function(index) {
|
||||
this.store.commit('setHoverRow', index);
|
||||
},
|
||||
}),
|
||||
|
||||
handleMouseLeave() {
|
||||
handleMouseLeave: debounce(30, function() {
|
||||
this.store.commit('setHoverRow', null);
|
||||
},
|
||||
}),
|
||||
|
||||
handleContextMenu(event, row) {
|
||||
this.handleEvent(event, row, 'contextmenu');
|
||||
|
@ -435,9 +312,169 @@ export default {
|
|||
table.$emit(`row-${name}`, row, column, event);
|
||||
},
|
||||
|
||||
handleExpandClick(row, e) {
|
||||
e.stopPropagation();
|
||||
this.store.toggleRowExpansion(row);
|
||||
rowRender(row, $index, treeRowData) {
|
||||
const { treeIndent, columns, firstDefaultColumnIndex } = this;
|
||||
const columnsHidden = columns.map((column, index) => this.isColumnHidden(index));
|
||||
const rowClasses = this.getRowClass(row, $index);
|
||||
let display = true;
|
||||
if (treeRowData) {
|
||||
rowClasses.push('el-table__row--level-' + treeRowData.level);
|
||||
display = treeRowData.display;
|
||||
}
|
||||
return (<tr
|
||||
v-show={display}
|
||||
style={ this.getRowStyle(row, $index) }
|
||||
class={ rowClasses }
|
||||
key={ this.getKeyOfRow(row, $index) }
|
||||
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 }>
|
||||
{
|
||||
columns.map((column, cellIndex) => {
|
||||
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
|
||||
if (!rowspan || !colspan) {
|
||||
return null;
|
||||
}
|
||||
const columnData = { ...column };
|
||||
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
|
||||
const data = {
|
||||
store: this.store,
|
||||
_self: this.context || this.table.$vnode.context,
|
||||
column: columnData,
|
||||
row,
|
||||
$index
|
||||
};
|
||||
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
|
||||
data.treeNode = {
|
||||
indent: treeRowData.level * treeIndent,
|
||||
level: treeRowData.level
|
||||
};
|
||||
// TODO: 优化这里的逻辑
|
||||
if (typeof treeRowData.expanded === 'boolean') {
|
||||
data.treeNode.expanded = treeRowData.expanded;
|
||||
// 表明是懒加载
|
||||
if ('loading' in treeRowData) {
|
||||
data.treeNode.loading = treeRowData.loading;
|
||||
}
|
||||
if ('noLazyChildren' in treeRowData) {
|
||||
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<td
|
||||
style={ this.getCellStyle($index, cellIndex, row, column) }
|
||||
class={ this.getCellClass($index, cellIndex, row, column) }
|
||||
rowspan={ rowspan }
|
||||
colspan={ colspan }
|
||||
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
|
||||
on-mouseleave={ this.handleCellMouseLeave }>
|
||||
{
|
||||
column.renderCell.call(
|
||||
this._renderProxy,
|
||||
this.$createElement,
|
||||
data,
|
||||
columnsHidden[cellIndex]
|
||||
)
|
||||
}
|
||||
</td>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tr>);
|
||||
},
|
||||
|
||||
wrappedRowRender(row, $index) {
|
||||
const store = this.store;
|
||||
const { isRowExpanded, assertRowKey } = store;
|
||||
const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states;
|
||||
if (this.hasExpandColumn && isRowExpanded(row)) {
|
||||
const renderExpanded = this.table.renderExpanded;
|
||||
const tr = this.rowRender(row, $index);
|
||||
if (!renderExpanded) {
|
||||
console.error('[Element Error]renderExpanded is required.');
|
||||
return tr;
|
||||
}
|
||||
// 使用二维数组,避免修改 $index
|
||||
return [[
|
||||
tr,
|
||||
<tr key={'expanded-row__' + tr.key}>
|
||||
<td colspan={ this.columnsCount } class="el-table__expanded-cell">
|
||||
{ renderExpanded(this.$createElement, { row, $index, store: this.store }) }
|
||||
</td>
|
||||
</tr>]];
|
||||
} else if (Object.keys(treeData).length) {
|
||||
assertRowKey();
|
||||
// TreeTable 时,rowKey 必须由用户设定,不使用 getKeyOfRow 计算
|
||||
// 在调用 rowRender 函数时,仍然会计算 rowKey,不太好的操作
|
||||
const key = getRowIdentity(row, rowKey);
|
||||
let cur = treeData[key];
|
||||
let treeRowData = null;
|
||||
if (cur) {
|
||||
treeRowData = {
|
||||
expanded: cur.expanded,
|
||||
level: cur.level,
|
||||
display: true
|
||||
};
|
||||
if (typeof cur.loaded === 'boolean' && cur.loaded) {
|
||||
treeRowData.noLazyChildren = !(cur.children && cur.children.length);
|
||||
}
|
||||
if (typeof cur.loading === 'boolean') {
|
||||
treeRowData.loading = cur.loading;
|
||||
}
|
||||
}
|
||||
const tmp = [this.rowRender(row, $index, treeRowData)];
|
||||
// 渲染嵌套数据
|
||||
if (cur) {
|
||||
// currentRow 记录的是 index,所以还需主动增加 TreeTable 的 index
|
||||
let i = 0;
|
||||
const traverse = (children, parent) => {
|
||||
if (!(children && children.length && parent)) return;
|
||||
children.forEach(node => {
|
||||
// 父节点的 display 状态影响子节点的显示状态
|
||||
const innerTreeRowData = {
|
||||
display: parent.display && parent.expanded,
|
||||
level: parent.level + 1
|
||||
};
|
||||
const childKey = getRowIdentity(node, rowKey);
|
||||
if (childKey === undefined || childKey === null) {
|
||||
throw new Error('for nested data item, row-key is required.');
|
||||
}
|
||||
cur = { ...treeData[childKey] };
|
||||
// 对于当前节点,分成有无子节点两种情况。
|
||||
// 如果包含子节点的,设置 expanded 属性。
|
||||
// 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。
|
||||
if (cur) {
|
||||
innerTreeRowData.expanded = cur.expanded;
|
||||
// 懒加载的某些节点,level 未知
|
||||
cur.level = cur.level || innerTreeRowData.level;
|
||||
cur.display = !!(cur.expanded && innerTreeRowData.display);
|
||||
if (typeof cur.loaded === 'boolean' && cur.loaded) {
|
||||
innerTreeRowData.noLazyChildren = !(cur.children && cur.children.length);
|
||||
}
|
||||
if (typeof cur.lazy === 'boolean') {
|
||||
innerTreeRowData.loading = cur.loading;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
|
||||
if (cur) {
|
||||
const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
|
||||
traverse(nodes, cur);
|
||||
}
|
||||
});
|
||||
};
|
||||
// 对于 root 节点,display 一定为 true
|
||||
cur.display = true;
|
||||
const nodes = lazyTreeNodeMap[key] || row[childrenColumnName];
|
||||
traverse(nodes, cur);
|
||||
}
|
||||
return tmp;
|
||||
} else {
|
||||
return this.rowRender(row, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,141 +1,9 @@
|
|||
import { cellStarts, cellForced, defaultRenderCell, treeCellPrefix } from './config';
|
||||
import { mergeOptions, parseWidth, parseMinWidth, compose } from './util';
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
import ElTag from 'element-ui/packages/tag';
|
||||
import objectAssign from 'element-ui/src/utils/merge';
|
||||
import { getPropByPath } from 'element-ui/src/utils/util';
|
||||
|
||||
let columnIdSeed = 1;
|
||||
|
||||
const defaults = {
|
||||
default: {
|
||||
order: ''
|
||||
},
|
||||
selection: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: '',
|
||||
className: 'el-table-column--selection'
|
||||
},
|
||||
expand: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: ''
|
||||
},
|
||||
index: {
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
realWidth: 48,
|
||||
order: ''
|
||||
}
|
||||
};
|
||||
|
||||
const forced = {
|
||||
selection: {
|
||||
renderHeader: function(h, { store }) {
|
||||
return <el-checkbox
|
||||
disabled={ store.states.data && store.states.data.length === 0 }
|
||||
indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
|
||||
nativeOn-click={ this.toggleAllSelection }
|
||||
value={ this.isAllSelected } />;
|
||||
},
|
||||
renderCell: function(h, { row, column, store, $index }) {
|
||||
return <el-checkbox
|
||||
nativeOn-click={ (event) => event.stopPropagation() }
|
||||
value={ store.isSelected(row) }
|
||||
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
|
||||
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false
|
||||
},
|
||||
index: {
|
||||
renderHeader: function(h, { column }) {
|
||||
return column.label || '#';
|
||||
},
|
||||
renderCell: function(h, { $index, column }) {
|
||||
let i = $index + 1;
|
||||
const index = column.index;
|
||||
|
||||
if (typeof index === 'number') {
|
||||
i = $index + index;
|
||||
} else if (typeof index === 'function') {
|
||||
i = index($index);
|
||||
}
|
||||
|
||||
return <div>{ i }</div>;
|
||||
},
|
||||
sortable: false
|
||||
},
|
||||
expand: {
|
||||
renderHeader: function(h, { column }) {
|
||||
return column.label || '';
|
||||
},
|
||||
renderCell: function(h, { row, store }, proxy) {
|
||||
const expanded = store.states.expandRows.indexOf(row) > -1;
|
||||
return <div class={ 'el-table__expand-icon ' + (expanded ? 'el-table__expand-icon--expanded' : '') }
|
||||
on-click={ e => proxy.handleExpandClick(row, e) }>
|
||||
<i class='el-icon el-icon-arrow-right'></i>
|
||||
</div>;
|
||||
},
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
className: 'el-table__expand-column'
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultColumn = function(type, options) {
|
||||
const column = {};
|
||||
|
||||
objectAssign(column, defaults[type || 'default']);
|
||||
|
||||
for (let name in options) {
|
||||
if (options.hasOwnProperty(name)) {
|
||||
const value = options[name];
|
||||
if (typeof value !== 'undefined') {
|
||||
column[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!column.minWidth) {
|
||||
column.minWidth = 80;
|
||||
}
|
||||
|
||||
column.realWidth = column.width === undefined ? column.minWidth : column.width;
|
||||
|
||||
return column;
|
||||
};
|
||||
|
||||
const DEFAULT_RENDER_CELL = function(h, { row, column, $index }) {
|
||||
const property = column.property;
|
||||
const value = property && getPropByPath(row, property).v;
|
||||
if (column && column.formatter) {
|
||||
return column.formatter(row, column, value, $index);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const parseWidth = (width) => {
|
||||
if (width !== undefined) {
|
||||
width = parseInt(width, 10);
|
||||
if (isNaN(width)) {
|
||||
width = null;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
};
|
||||
|
||||
const parseMinWidth = (minWidth) => {
|
||||
if (minWidth !== undefined) {
|
||||
minWidth = parseInt(minWidth, 10);
|
||||
if (isNaN(minWidth)) {
|
||||
minWidth = 80;
|
||||
}
|
||||
}
|
||||
return minWidth;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ElTableColumn',
|
||||
|
||||
|
@ -153,7 +21,7 @@ export default {
|
|||
minWidth: {},
|
||||
renderHeader: Function,
|
||||
sortable: {
|
||||
type: [String, Boolean],
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
sortMethod: Function,
|
||||
|
@ -162,7 +30,6 @@ export default {
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
context: {},
|
||||
columnKey: String,
|
||||
align: String,
|
||||
headerAlign: String,
|
||||
|
@ -199,17 +66,6 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.row = {};
|
||||
this.column = {};
|
||||
this.$index = 0;
|
||||
},
|
||||
|
||||
components: {
|
||||
ElCheckbox,
|
||||
ElTag
|
||||
},
|
||||
|
||||
computed: {
|
||||
owner() {
|
||||
let parent = this.$parent;
|
||||
|
@ -218,125 +74,237 @@ export default {
|
|||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
columnOrTableParent() {
|
||||
let parent = this.$parent;
|
||||
while (parent && !parent.tableId && !parent.columnId) {
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
realWidth() {
|
||||
return parseWidth(this.width);
|
||||
},
|
||||
|
||||
realMinWidth() {
|
||||
return parseMinWidth(this.minWidth);
|
||||
},
|
||||
|
||||
realAlign() {
|
||||
return this.align ? 'is-' + this.align : null;
|
||||
},
|
||||
|
||||
realHeaderAlign() {
|
||||
return this.headerAlign ? 'is-' + this.headerAlign : this.realAlign;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.customRender = this.$options.render;
|
||||
this.$options.render = h => h('div', this.$slots.default);
|
||||
|
||||
let parent = this.columnOrTableParent;
|
||||
let owner = this.owner;
|
||||
this.isSubColumn = owner !== parent;
|
||||
this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
|
||||
|
||||
let type = this.type;
|
||||
|
||||
const width = parseWidth(this.width);
|
||||
const minWidth = parseMinWidth(this.minWidth);
|
||||
|
||||
let isColumnGroup = false;
|
||||
|
||||
let column = getDefaultColumn(type, {
|
||||
id: this.columnId,
|
||||
columnKey: this.columnKey,
|
||||
label: this.label,
|
||||
className: this.className,
|
||||
labelClassName: this.labelClassName,
|
||||
property: this.prop || this.property,
|
||||
type,
|
||||
renderCell: null,
|
||||
renderHeader: this.renderHeader,
|
||||
minWidth,
|
||||
width,
|
||||
isColumnGroup,
|
||||
context: this.context,
|
||||
align: this.align ? 'is-' + this.align : null,
|
||||
headerAlign: this.headerAlign ? 'is-' + this.headerAlign : (this.align ? 'is-' + this.align : null),
|
||||
sortable: this.sortable === '' ? true : this.sortable,
|
||||
sortMethod: this.sortMethod,
|
||||
sortBy: this.sortBy,
|
||||
resizable: this.resizable,
|
||||
showOverflowTooltip: this.showOverflowTooltip || this.showTooltipWhenOverflow,
|
||||
formatter: this.formatter,
|
||||
selectable: this.selectable,
|
||||
reserveSelection: this.reserveSelection,
|
||||
fixed: this.fixed === '' ? true : this.fixed,
|
||||
filterMethod: this.filterMethod,
|
||||
filters: this.filters,
|
||||
filterable: this.filters || this.filterMethod,
|
||||
filterMultiple: this.filterMultiple,
|
||||
filterOpened: false,
|
||||
filteredValue: this.filteredValue || [],
|
||||
filterPlacement: this.filterPlacement || '',
|
||||
index: this.index,
|
||||
sortOrders: this.sortOrders
|
||||
methods: {
|
||||
getPropsData(...props) {
|
||||
return props.reduce((prev, cur) => {
|
||||
if (Array.isArray(cur)) {
|
||||
cur.forEach((key) => {
|
||||
prev[key] = this[key];
|
||||
});
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
},
|
||||
|
||||
let source = forced[type] || {};
|
||||
Object.keys(source).forEach((prop) => {
|
||||
getColumnElIndex(children, child) {
|
||||
return [].indexOf.call(children, child);
|
||||
},
|
||||
|
||||
setColumnWidth(column) {
|
||||
if (this.realWidth) {
|
||||
column.width = this.realWidth;
|
||||
}
|
||||
if (this.realMinWidth) {
|
||||
column.minWidth = this.realMinWidth;
|
||||
}
|
||||
if (!column.minWidth) {
|
||||
column.minWidth = 80;
|
||||
}
|
||||
column.realWidth = column.width === undefined ? column.minWidth : column.width;
|
||||
return column;
|
||||
},
|
||||
|
||||
setColumnForcedProps(column) {
|
||||
// 对于特定类型的 column,某些属性不允许设置
|
||||
const type = column.type;
|
||||
const source = cellForced[type] || {};
|
||||
Object.keys(source).forEach(prop => {
|
||||
let value = source[prop];
|
||||
if (value !== undefined) {
|
||||
if (prop === 'renderHeader') {
|
||||
if (type === 'selection' && column[prop]) {
|
||||
console.warn('[Element Warn][TableColumn]Selection column doesn\'t allow to set render-header function.');
|
||||
} else {
|
||||
value = column[prop] || value;
|
||||
}
|
||||
}
|
||||
column[prop] = prop === 'className' ? `${column[prop]} ${value}` : value;
|
||||
}
|
||||
});
|
||||
return column;
|
||||
},
|
||||
|
||||
// Deprecation warning for renderHeader property
|
||||
setColumnRenders(column) {
|
||||
const specialTypes = Object.keys(cellForced);
|
||||
// renderHeader 属性不推荐使用。
|
||||
if (this.renderHeader) {
|
||||
console.warn('[Element Warn][TableColumn]Comparing to render-header, scoped-slot header is easier to use. We recommend users to use scoped-slot header.');
|
||||
}
|
||||
|
||||
this.columnConfig = column;
|
||||
|
||||
let renderCell = column.renderCell;
|
||||
let _self = this;
|
||||
|
||||
if (type === 'expand') {
|
||||
owner.renderExpanded = function(h, data) {
|
||||
return _self.$scopedSlots.default
|
||||
? _self.$scopedSlots.default(data)
|
||||
: _self.$slots.default;
|
||||
} else if (specialTypes.indexOf(column.type) === -1) {
|
||||
column.renderHeader = (h, scope) => {
|
||||
const renderHeader = this.$scopedSlots.header;
|
||||
return renderHeader ? renderHeader(scope) : column.label;
|
||||
};
|
||||
}
|
||||
|
||||
column.renderCell = function(h, data) {
|
||||
return <div class="cell">{ renderCell(h, data, this._renderProxy) }</div>;
|
||||
let originRenderCell = column.renderCell;
|
||||
// TODO: 这里的实现调整
|
||||
if (column.type === 'expand') {
|
||||
// 对于展开行,renderCell 不允许配置的。在上一步中已经设置过,这里需要简单封装一下。
|
||||
column.renderCell = (h, data) => (<div class="cell">
|
||||
{ originRenderCell(h, data) }
|
||||
</div>);
|
||||
this.owner.renderExpanded = (h, data) => {
|
||||
return this.$scopedSlots.default
|
||||
? this.$scopedSlots.default(data)
|
||||
: this.$slots.default;
|
||||
};
|
||||
|
||||
return;
|
||||
} else {
|
||||
originRenderCell = originRenderCell || defaultRenderCell;
|
||||
// 对 renderCell 进行包装
|
||||
column.renderCell = (h, data) => {
|
||||
let children = null;
|
||||
if (this.$scopedSlots.default) {
|
||||
children = this.$scopedSlots.default(data);
|
||||
} else {
|
||||
children = originRenderCell(h, data);
|
||||
}
|
||||
|
||||
column.renderCell = function(h, data) {
|
||||
if (_self.$scopedSlots.default) {
|
||||
renderCell = () => _self.$scopedSlots.default(data);
|
||||
const prefix = treeCellPrefix(h, data);
|
||||
const props = {
|
||||
class: 'cell',
|
||||
style: {}
|
||||
};
|
||||
if (column.showOverflowTooltip) {
|
||||
props.class += ' el-tooltip';
|
||||
props.style = {width: (data.column.realWidth || data.column.width) - 1 + 'px'};
|
||||
}
|
||||
|
||||
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'} }>{ children }</div>
|
||||
: (<div class="cell">
|
||||
return (<div { ...props }>
|
||||
{ prefix }
|
||||
{ children }
|
||||
</div>);
|
||||
};
|
||||
}
|
||||
return column;
|
||||
},
|
||||
|
||||
registerNormalWatchers() {
|
||||
const props = ['label', 'property', 'filters', 'filterMultiple', 'sortable', 'index', 'formatter', 'className', 'labelClassName'];
|
||||
// 一些属性具有别名
|
||||
const aliases = {
|
||||
prop: 'property',
|
||||
realAlign: 'align',
|
||||
realHeaderAlign: 'headerAlign',
|
||||
realWidth: 'width'
|
||||
};
|
||||
const allAliases = props.reduce((prev, cur) => {
|
||||
prev[cur] = cur;
|
||||
return prev;
|
||||
}, aliases);
|
||||
|
||||
Object.keys(allAliases).forEach(key => {
|
||||
const columnKey = aliases[key];
|
||||
|
||||
this.$watch(key, (newVal) => {
|
||||
this.columnConfig[columnKey] = newVal;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
registerComplexWatchers() {
|
||||
const props = ['fixed'];
|
||||
const aliases = {
|
||||
realWidth: 'width',
|
||||
realMinWidth: 'minWidth'
|
||||
};
|
||||
const allAliases = props.reduce((prev, cur) => {
|
||||
prev[cur] = cur;
|
||||
return prev;
|
||||
}, aliases);
|
||||
|
||||
Object.keys(allAliases).forEach(key => {
|
||||
const columnKey = aliases[key];
|
||||
|
||||
this.$watch(key, (newVal) => {
|
||||
this.columnConfig[columnKey] = newVal;
|
||||
const updateColumns = columnKey === 'fixed';
|
||||
this.owner.store.scheduleLayout(updateColumns);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
ElCheckbox
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
this.row = {};
|
||||
this.column = {};
|
||||
this.$index = 0;
|
||||
this.columnId = '';
|
||||
},
|
||||
|
||||
created() {
|
||||
const parent = this.columnOrTableParent;
|
||||
this.isSubColumn = this.owner !== parent;
|
||||
this.columnId = (parent.tableId || parent.columnId) + '_column_' + columnIdSeed++;
|
||||
|
||||
const type = this.type || 'default';
|
||||
const sortable = this.sortable === '' ? true : this.sortable;
|
||||
const defaults = {
|
||||
...cellStarts[type],
|
||||
id: this.columnId,
|
||||
type: type,
|
||||
property: this.prop || this.property,
|
||||
align: this.realAlign,
|
||||
headerAlign: this.realHeaderAlign,
|
||||
showOverflowTooltip: this.showOverflowTooltip || this.showTooltipWhenOverflow,
|
||||
// filter 相关属性
|
||||
filterable: this.filters || this.filterMethod,
|
||||
filteredValue: [],
|
||||
filterPlacement: '',
|
||||
isColumnGroup: false,
|
||||
filterOpened: false,
|
||||
// sort 相关属性
|
||||
sortable: sortable,
|
||||
// index 列
|
||||
index: this.index
|
||||
};
|
||||
|
||||
const basicProps = ['columnKey', 'label', 'className', 'labelClassName', 'type', 'renderHeader', 'resizable', 'formatter', 'fixed', 'resizable'];
|
||||
const sortProps = ['sortMethod', 'sortBy', 'sortOrders'];
|
||||
const selectProps = ['selectable', 'reserveSelection'];
|
||||
const filterProps = ['filterMethod', 'filters', 'filterMultiple', 'filterOpened', 'filteredValue', 'filterPlacement'];
|
||||
|
||||
let column = this.getPropsData(basicProps, sortProps, selectProps, filterProps);
|
||||
column = mergeOptions(defaults, column);
|
||||
|
||||
// 注意 compose 中函数执行的顺序是从右到左
|
||||
const chains = compose(this.setColumnRenders, this.setColumnWidth, this.setColumnForcedProps);
|
||||
column = chains(column);
|
||||
|
||||
this.columnConfig = column;
|
||||
|
||||
// 注册 watcher
|
||||
this.registerNormalWatchers();
|
||||
this.registerComplexWatchers();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const owner = this.owner;
|
||||
const parent = this.columnOrTableParent;
|
||||
const children = this.isSubColumn ? parent.$el.children : parent.$refs.hiddenColumns.children;
|
||||
const columnIndex = this.getColumnElIndex(children, this.$el);
|
||||
|
||||
owner.store.commit('insertColumn', this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
|
@ -345,150 +313,8 @@ export default {
|
|||
this.owner.store.commit('removeColumn', this.columnConfig, this.isSubColumn ? parent.columnConfig : null);
|
||||
},
|
||||
|
||||
watch: {
|
||||
label(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.label = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
prop(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.property = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
property(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.property = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
filters(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.filters = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
filterMultiple(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.filterMultiple = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
align(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.align = newVal ? 'is-' + newVal : null;
|
||||
|
||||
if (!this.headerAlign) {
|
||||
this.columnConfig.headerAlign = newVal ? 'is-' + newVal : null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
headerAlign(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.headerAlign = 'is-' + (newVal ? newVal : this.align);
|
||||
}
|
||||
},
|
||||
|
||||
width(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.width = parseWidth(newVal);
|
||||
this.owner.store.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
minWidth(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.minWidth = parseMinWidth(newVal);
|
||||
this.owner.store.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
fixed(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.fixed = newVal;
|
||||
this.owner.store.scheduleLayout(true);
|
||||
}
|
||||
},
|
||||
|
||||
sortable(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.sortable = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
index(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.index = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
formatter(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.formatter = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
className(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.className = newVal;
|
||||
}
|
||||
},
|
||||
|
||||
labelClassName(newVal) {
|
||||
if (this.columnConfig) {
|
||||
this.columnConfig.labelClassName = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
let columnIndex;
|
||||
|
||||
if (!this.isSubColumn) {
|
||||
columnIndex = [].indexOf.call(parent.$refs.hiddenColumns.children, this.$el);
|
||||
} else {
|
||||
columnIndex = [].indexOf.call(parent.$el.children, this.$el);
|
||||
}
|
||||
|
||||
if (this.$scopedSlots.header) {
|
||||
if (this.type === 'selection') {
|
||||
console.warn('[Element Warn][TableColumn]Selection column doesn\'t allow to set scoped-slot header.');
|
||||
} else {
|
||||
this.columnConfig.renderHeader = (h, scope) => this.$scopedSlots.header(scope);
|
||||
}
|
||||
}
|
||||
|
||||
owner.store.commit('insertColumn', this.columnConfig, columnIndex, this.isSubColumn ? parent.columnConfig : null);
|
||||
render(h) {
|
||||
// slots 也要渲染,需要计算合并表头
|
||||
return h('div', this.$slots.default);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import LayoutObserver from './layout-observer';
|
||||
import { mapStates } from './store/helper';
|
||||
|
||||
export default {
|
||||
name: 'ElTableFooter',
|
||||
|
@ -49,7 +50,7 @@ export default {
|
|||
border="0">
|
||||
<colgroup>
|
||||
{
|
||||
this._l(this.columns, column => <col name={ column.id } />)
|
||||
this.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
}
|
||||
{
|
||||
this.hasGutter ? <col name="gutter" /> : ''
|
||||
|
@ -58,8 +59,8 @@ export default {
|
|||
<tbody class={ [{ 'has-gutter': this.hasGutter }] }>
|
||||
<tr>
|
||||
{
|
||||
this._l(this.columns, (column, cellIndex) =>
|
||||
<td
|
||||
this.columns.map((column, cellIndex) => <td
|
||||
key={cellIndex}
|
||||
colspan={ column.colSpan }
|
||||
rowspan={ column.rowSpan }
|
||||
class={ this.getRowClasses(column, cellIndex) }>
|
||||
|
@ -68,8 +69,7 @@ export default {
|
|||
sums[cellIndex]
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
</td>)
|
||||
}
|
||||
{
|
||||
this.hasGutter ? <th class="gutter"></th> : ''
|
||||
|
@ -104,37 +104,19 @@ export default {
|
|||
return this.$parent;
|
||||
},
|
||||
|
||||
isAllSelected() {
|
||||
return this.store.states.isAllSelected;
|
||||
},
|
||||
|
||||
columnsCount() {
|
||||
return this.store.states.columns.length;
|
||||
},
|
||||
|
||||
leftFixedCount() {
|
||||
return this.store.states.fixedColumns.length;
|
||||
},
|
||||
|
||||
leftFixedLeafCount() {
|
||||
return this.store.states.fixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
rightFixedLeafCount() {
|
||||
return this.store.states.rightFixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
rightFixedCount() {
|
||||
return this.store.states.rightFixedColumns.length;
|
||||
},
|
||||
|
||||
columns() {
|
||||
return this.store.states.columns;
|
||||
},
|
||||
|
||||
hasGutter() {
|
||||
return !this.fixed && this.tableLayout.gutterWidth;
|
||||
}
|
||||
},
|
||||
|
||||
...mapStates({
|
||||
columns: 'columns',
|
||||
isAllSelected: 'isAllSelected',
|
||||
leftFixedLeafCount: 'fixedLeafColumnsLength',
|
||||
rightFixedLeafCount: 'rightFixedLeafColumnsLength',
|
||||
columnsCount: states => states.columns.length,
|
||||
leftFixedCount: states => states.fixedColumns.length,
|
||||
rightFixedCount: states => states.rightFixedColumns.length
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
import { hasClass, addClass, removeClass } from 'element-ui/src/utils/dom';
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
import ElTag from 'element-ui/packages/tag';
|
||||
import Vue from 'vue';
|
||||
import FilterPanel from './filter-panel.vue';
|
||||
import LayoutObserver from './layout-observer';
|
||||
import { mapStates } from './store/helper';
|
||||
|
||||
const getAllColumns = (columns) => {
|
||||
const result = [];
|
||||
|
@ -82,7 +82,7 @@ export default {
|
|||
border="0">
|
||||
<colgroup>
|
||||
{
|
||||
this._l(this.columns, column => <col name={ column.id } />)
|
||||
this.columns.map(column => <col name={ column.id } key={column.id} />)
|
||||
}
|
||||
{
|
||||
this.hasGutter ? <col name="gutter" /> : ''
|
||||
|
@ -96,8 +96,7 @@ export default {
|
|||
class={ this.getHeaderRowClass(rowIndex) }
|
||||
>
|
||||
{
|
||||
this._l(columns, (column, cellIndex) =>
|
||||
<th
|
||||
columns.map((column, cellIndex) => (<th
|
||||
colspan={ column.colSpan }
|
||||
rowspan={ column.rowSpan }
|
||||
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
|
||||
|
@ -115,23 +114,26 @@ export default {
|
|||
: column.label
|
||||
}
|
||||
{
|
||||
column.sortable
|
||||
? <span class="caret-wrapper" on-click={ ($event) => this.handleSortClick($event, column) }>
|
||||
<i class="sort-caret ascending" on-click={ ($event) => this.handleSortClick($event, column, 'ascending') }>
|
||||
column.sortable ? (<span
|
||||
class="caret-wrapper"
|
||||
on-click={ ($event) => this.handleSortClick($event, column) }>
|
||||
<i class="sort-caret ascending"
|
||||
on-click={ ($event) => this.handleSortClick($event, column, 'ascending') }>
|
||||
</i>
|
||||
<i class="sort-caret descending" on-click={ ($event) => this.handleSortClick($event, column, 'descending') }>
|
||||
<i class="sort-caret descending"
|
||||
on-click={ ($event) => this.handleSortClick($event, column, 'descending') }>
|
||||
</i>
|
||||
</span>
|
||||
: ''
|
||||
</span>) : ''
|
||||
}
|
||||
{
|
||||
column.filterable
|
||||
? <span class="el-table__column-filter-trigger" on-click={ ($event) => this.handleFilterClick($event, column) }><i class={ ['el-icon-arrow-down', column.filterOpened ? 'el-icon-arrow-up' : ''] }></i></span>
|
||||
: ''
|
||||
column.filterable ? (<span
|
||||
class="el-table__column-filter-trigger"
|
||||
on-click={ ($event) => this.handleFilterClick($event, column) }>
|
||||
<i class={ ['el-icon-arrow-down', column.filterOpened ? 'el-icon-arrow-up' : ''] }></i>
|
||||
</span>) : ''
|
||||
}
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
</th>))
|
||||
}
|
||||
{
|
||||
this.hasGutter ? <th class="gutter"></th> : ''
|
||||
|
@ -162,8 +164,7 @@ export default {
|
|||
},
|
||||
|
||||
components: {
|
||||
ElCheckbox,
|
||||
ElTag
|
||||
ElCheckbox
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -171,37 +172,19 @@ export default {
|
|||
return this.$parent;
|
||||
},
|
||||
|
||||
isAllSelected() {
|
||||
return this.store.states.isAllSelected;
|
||||
},
|
||||
|
||||
columnsCount() {
|
||||
return this.store.states.columns.length;
|
||||
},
|
||||
|
||||
leftFixedCount() {
|
||||
return this.store.states.fixedColumns.length;
|
||||
},
|
||||
|
||||
rightFixedCount() {
|
||||
return this.store.states.rightFixedColumns.length;
|
||||
},
|
||||
|
||||
leftFixedLeafCount() {
|
||||
return this.store.states.fixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
rightFixedLeafCount() {
|
||||
return this.store.states.rightFixedLeafColumnsLength;
|
||||
},
|
||||
|
||||
columns() {
|
||||
return this.store.states.columns;
|
||||
},
|
||||
|
||||
hasGutter() {
|
||||
return !this.fixed && this.tableLayout.gutterWidth;
|
||||
}
|
||||
},
|
||||
|
||||
...mapStates({
|
||||
columns: 'columns',
|
||||
isAllSelected: 'isAllSelected',
|
||||
leftFixedLeafCount: 'fixedLeafColumnsLength',
|
||||
rightFixedLeafCount: 'rightFixedLeafColumnsLength',
|
||||
columnsCount: states => states.columns.length,
|
||||
leftFixedCount: states => states.fixedColumns.length,
|
||||
rightFixedCount: states => states.rightFixedColumns.length
|
||||
})
|
||||
},
|
||||
|
||||
created() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
|
||||
import Vue from 'vue';
|
||||
import scrollbarWidth from 'element-ui/src/utils/scrollbar-width';
|
||||
import { parseHeight } from './util';
|
||||
|
||||
class TableLayout {
|
||||
constructor(options) {
|
||||
|
@ -41,7 +42,7 @@ class TableLayout {
|
|||
|
||||
updateScrollY() {
|
||||
const height = this.height;
|
||||
if (typeof height !== 'string' && typeof height !== 'number') return;
|
||||
if (height === null) return;
|
||||
const bodyWrapper = this.table.bodyWrapper;
|
||||
if (this.table.$el && bodyWrapper) {
|
||||
const body = bodyWrapper.querySelector('.el-table__body');
|
||||
|
@ -52,49 +53,19 @@ class TableLayout {
|
|||
setHeight(value, prop = 'height') {
|
||||
if (Vue.prototype.$isServer) return;
|
||||
const el = this.table.$el;
|
||||
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
||||
value = Number(value);
|
||||
}
|
||||
value = parseHeight(value);
|
||||
this.height = value;
|
||||
|
||||
if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));
|
||||
|
||||
if (typeof value === 'number') {
|
||||
el.style[prop] = value + 'px';
|
||||
|
||||
this.updateElsHeight();
|
||||
} else if (typeof value === 'string') {
|
||||
el.style[prop] = value;
|
||||
if (value) {
|
||||
el.style[prop] = `${value}px`;
|
||||
this.updateElsHeight();
|
||||
}
|
||||
}
|
||||
|
||||
setMaxHeight(value) {
|
||||
return this.setHeight(value, 'max-height');
|
||||
}
|
||||
|
||||
updateElsHeight() {
|
||||
if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
|
||||
const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs;
|
||||
this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;
|
||||
|
||||
if (this.showHeader && !headerWrapper) return;
|
||||
const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
|
||||
if (this.showHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
|
||||
return Vue.nextTick(() => this.updateElsHeight());
|
||||
}
|
||||
const tableHeight = this.tableHeight = this.table.$el.clientHeight;
|
||||
if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) {
|
||||
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
|
||||
this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
|
||||
}
|
||||
this.fixedBodyHeight = this.scrollX ? this.bodyHeight - this.gutterWidth : this.bodyHeight;
|
||||
|
||||
const noData = !this.table.data || this.table.data.length === 0;
|
||||
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
|
||||
|
||||
this.updateScrollY();
|
||||
this.notifyObservers('scrollable');
|
||||
this.setHeight(value, 'max-height');
|
||||
}
|
||||
|
||||
getFlattenColumns() {
|
||||
|
@ -111,6 +82,30 @@ class TableLayout {
|
|||
return flattenColumns;
|
||||
}
|
||||
|
||||
updateElsHeight() {
|
||||
if (!this.table.$ready) return Vue.nextTick(() => this.updateElsHeight());
|
||||
const { headerWrapper, appendWrapper, footerWrapper } = this.table.$refs;
|
||||
this.appendHeight = appendWrapper ? appendWrapper.offsetHeight : 0;
|
||||
|
||||
if (this.showHeader && !headerWrapper) return;
|
||||
const headerHeight = this.headerHeight = !this.showHeader ? 0 : headerWrapper.offsetHeight;
|
||||
if (this.showHeader && headerWrapper.offsetWidth > 0 && (this.table.columns || []).length > 0 && headerHeight < 2) {
|
||||
return Vue.nextTick(() => this.updateElsHeight());
|
||||
}
|
||||
const tableHeight = this.tableHeight = this.table.$el.clientHeight;
|
||||
const footerHeight = this.footerHeight = footerWrapper ? footerWrapper.offsetHeight : 0;
|
||||
if (this.height !== null) {
|
||||
this.bodyHeight = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0);
|
||||
}
|
||||
this.fixedBodyHeight = this.scrollX ? (this.bodyHeight - this.gutterWidth) : this.bodyHeight;
|
||||
|
||||
const noData = !this.table.data || this.table.data.length === 0;
|
||||
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
|
||||
|
||||
this.updateScrollY();
|
||||
this.notifyObservers('scrollable');
|
||||
}
|
||||
|
||||
updateColumnsWidth() {
|
||||
if (Vue.prototype.$isServer) return;
|
||||
const fit = this.fit;
|
||||
|
|
|
@ -1,753 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import merge from 'element-ui/src/utils/merge';
|
||||
import { orderBy, getColumnById, getRowIdentity, getColumnByKey } from './util';
|
||||
|
||||
const sortData = (data, states) => {
|
||||
const sortingColumn = states.sortingColumn;
|
||||
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
|
||||
return data;
|
||||
}
|
||||
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) {
|
||||
const arrayMap = {};
|
||||
(array || []).forEach((row, index) => {
|
||||
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
|
||||
});
|
||||
return arrayMap;
|
||||
};
|
||||
|
||||
const toggleRowSelection = function(states, row, selected) {
|
||||
let changed = false;
|
||||
const selection = states.selection;
|
||||
const index = selection.indexOf(row);
|
||||
if (typeof selected === 'undefined') {
|
||||
if (index === -1) {
|
||||
selection.push(row);
|
||||
changed = true;
|
||||
} else {
|
||||
selection.splice(index, 1);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (selected && index === -1) {
|
||||
selection.push(row);
|
||||
changed = true;
|
||||
} else if (!selected && index > -1) {
|
||||
selection.splice(index, 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
};
|
||||
|
||||
const toggleRowExpansion = function(states, row, expanded) {
|
||||
let changed = false;
|
||||
const expandRows = states.expandRows;
|
||||
if (typeof expanded !== 'undefined') {
|
||||
const index = expandRows.indexOf(row);
|
||||
if (expanded) {
|
||||
if (index === -1) {
|
||||
expandRows.push(row);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
expandRows.splice(index, 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const index = expandRows.indexOf(row);
|
||||
if (index === -1) {
|
||||
expandRows.push(row);
|
||||
changed = true;
|
||||
} else {
|
||||
expandRows.splice(index, 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
};
|
||||
|
||||
const TableStore = function(table, initialState = {}) {
|
||||
if (!table) {
|
||||
throw new Error('Table is required.');
|
||||
}
|
||||
this.table = table;
|
||||
|
||||
this.states = {
|
||||
rowKey: null,
|
||||
_columns: [],
|
||||
originColumns: [],
|
||||
columns: [],
|
||||
fixedColumns: [],
|
||||
rightFixedColumns: [],
|
||||
leafColumns: [],
|
||||
fixedLeafColumns: [],
|
||||
rightFixedLeafColumns: [],
|
||||
leafColumnsLength: 0,
|
||||
fixedLeafColumnsLength: 0,
|
||||
rightFixedLeafColumnsLength: 0,
|
||||
isComplex: false,
|
||||
filteredData: null,
|
||||
data: null,
|
||||
sortingColumn: null,
|
||||
sortProp: null,
|
||||
sortOrder: null,
|
||||
isAllSelected: false,
|
||||
selection: [],
|
||||
reserveSelection: false,
|
||||
selectable: null,
|
||||
currentRow: null,
|
||||
hoverRow: null,
|
||||
filters: {},
|
||||
expandRows: [],
|
||||
defaultExpandAll: false,
|
||||
selectOnIndeterminate: false,
|
||||
treeData: {},
|
||||
indent: 16,
|
||||
lazy: false,
|
||||
lazyTreeNodeMap: {}
|
||||
};
|
||||
|
||||
this._toggleAllSelection = debounce(10, function(states) {
|
||||
const data = states.data || [];
|
||||
if (data.length === 0) return;
|
||||
const selection = this.states.selection;
|
||||
// when only some rows are selected (but not all), select or deselect all of them
|
||||
// depending on the value of selectOnIndeterminate
|
||||
const value = states.selectOnIndeterminate
|
||||
? !states.isAllSelected
|
||||
: !(states.isAllSelected || selection.length);
|
||||
let selectionChanged = false;
|
||||
data.forEach((item, index) => {
|
||||
if (states.selectable) {
|
||||
if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
} else {
|
||||
if (toggleRowSelection(states, item, value)) {
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const table = this.table;
|
||||
if (selectionChanged) {
|
||||
table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
}
|
||||
table.$emit('select-all', selection);
|
||||
states.isAllSelected = value;
|
||||
});
|
||||
|
||||
for (let prop in initialState) {
|
||||
if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
|
||||
this.states[prop] = initialState[prop];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.mutations = {
|
||||
setData(states, data) {
|
||||
const dataInstanceChanged = states._data !== data;
|
||||
states._data = data;
|
||||
|
||||
Object.keys(states.filters).forEach((columnId) => {
|
||||
const values = states.filters[columnId];
|
||||
if (!values || values.length === 0) return;
|
||||
const column = getColumnById(this.states, columnId);
|
||||
if (column && column.filterMethod) {
|
||||
data = data.filter((row) => {
|
||||
return values.some(value => column.filterMethod.call(null, value, row, column));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
states.filteredData = data;
|
||||
states.data = sortData((data || []), states);
|
||||
|
||||
this.updateCurrentRow();
|
||||
|
||||
const rowKey = states.rowKey;
|
||||
|
||||
if (!states.reserveSelection) {
|
||||
if (dataInstanceChanged) {
|
||||
this.clearSelection();
|
||||
} else {
|
||||
this.cleanSelection();
|
||||
}
|
||||
this.updateAllSelected();
|
||||
} else {
|
||||
if (rowKey) {
|
||||
const selection = states.selection;
|
||||
const selectedMap = getKeysMap(selection, rowKey);
|
||||
|
||||
states.data.forEach((row) => {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
const rowInfo = selectedMap[rowId];
|
||||
if (rowInfo) {
|
||||
selection[rowInfo.index] = row;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateAllSelected();
|
||||
} else {
|
||||
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
const defaultExpandAll = states.defaultExpandAll;
|
||||
if (defaultExpandAll) {
|
||||
this.states.expandRows = (states.data || []).slice(0);
|
||||
} else if (rowKey) {
|
||||
// update expandRows to new rows according to rowKey
|
||||
const ids = getKeysMap(this.states.expandRows, rowKey);
|
||||
let expandRows = [];
|
||||
for (const row of states.data) {
|
||||
const rowId = getRowIdentity(row, rowKey);
|
||||
if (ids[rowId]) {
|
||||
expandRows.push(row);
|
||||
}
|
||||
}
|
||||
this.states.expandRows = expandRows;
|
||||
} else {
|
||||
// clear the old rows
|
||||
this.states.expandRows = [];
|
||||
}
|
||||
|
||||
Vue.nextTick(() => this.table.updateScrollY());
|
||||
},
|
||||
|
||||
changeSortCondition(states, options) {
|
||||
states.data = sortData((states.filteredData || states._data || []), states);
|
||||
|
||||
if (!options || !(options.silent || options.init)) {
|
||||
this.table.$emit('sort-change', {
|
||||
column: this.states.sortingColumn,
|
||||
prop: this.states.sortProp,
|
||||
order: this.states.sortOrder
|
||||
});
|
||||
}
|
||||
|
||||
Vue.nextTick(() => this.table.updateScrollY());
|
||||
},
|
||||
|
||||
sort(states, options) {
|
||||
const { prop, order, init } = options;
|
||||
if (prop) {
|
||||
states.sortProp = prop;
|
||||
states.sortOrder = order || 'ascending';
|
||||
Vue.nextTick(() => {
|
||||
for (let i = 0, length = states.columns.length; i < length; i++) {
|
||||
let column = states.columns[i];
|
||||
if (column.property === states.sortProp) {
|
||||
column.order = states.sortOrder;
|
||||
states.sortingColumn = column;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (states.sortingColumn) {
|
||||
this.commit('changeSortCondition', {
|
||||
init: init
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
filterChange(states, options) {
|
||||
let { column, values, silent, multi } = options;
|
||||
if (values && !Array.isArray(values)) {
|
||||
values = [values];
|
||||
}
|
||||
const filters = {};
|
||||
|
||||
if (multi) {
|
||||
column.forEach(col => {
|
||||
states.filters[col.id] = values;
|
||||
filters[col.columnKey || col.id] = values;
|
||||
});
|
||||
} else {
|
||||
const prop = column.property;
|
||||
|
||||
if (prop) {
|
||||
states.filters[column.id] = values;
|
||||
filters[column.columnKey || column.id] = values;
|
||||
}
|
||||
}
|
||||
|
||||
let data = states._data;
|
||||
|
||||
Object.keys(states.filters).forEach((columnId) => {
|
||||
const values = states.filters[columnId];
|
||||
if (!values || values.length === 0) return;
|
||||
const column = getColumnById(this.states, columnId);
|
||||
if (column && column.filterMethod) {
|
||||
data = data.filter((row) => {
|
||||
return values.some(value => column.filterMethod.call(null, value, row, column));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
states.filteredData = data;
|
||||
states.data = sortData(data, states);
|
||||
|
||||
if (!silent) {
|
||||
this.table.$emit('filter-change', filters);
|
||||
}
|
||||
|
||||
Vue.nextTick(() => this.table.updateScrollY());
|
||||
},
|
||||
|
||||
insertColumn(states, column, index, parent) {
|
||||
let array = states._columns;
|
||||
if (parent) {
|
||||
array = parent.children;
|
||||
if (!array) array = parent.children = [];
|
||||
}
|
||||
|
||||
if (typeof index !== 'undefined') {
|
||||
array.splice(index, 0, column);
|
||||
} else {
|
||||
array.push(column);
|
||||
}
|
||||
|
||||
if (column.type === 'selection') {
|
||||
states.selectable = column.selectable;
|
||||
states.reserveSelection = column.reserveSelection;
|
||||
}
|
||||
|
||||
if (this.table.$ready) {
|
||||
this.updateColumns(); // hack for dynamics insert column
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
removeColumn(states, column, parent) {
|
||||
let array = states._columns;
|
||||
if (parent) {
|
||||
array = parent.children;
|
||||
if (!array) array = parent.children = [];
|
||||
}
|
||||
if (array) {
|
||||
array.splice(array.indexOf(column), 1);
|
||||
}
|
||||
|
||||
if (this.table.$ready) {
|
||||
this.updateColumns(); // hack for dynamics remove column
|
||||
this.scheduleLayout();
|
||||
}
|
||||
},
|
||||
|
||||
setHoverRow(states, row) {
|
||||
states.hoverRow = row;
|
||||
},
|
||||
|
||||
setCurrentRow(states, row) {
|
||||
const oldCurrentRow = states.currentRow;
|
||||
states.currentRow = row;
|
||||
|
||||
if (oldCurrentRow !== row) {
|
||||
this.table.$emit('current-change', row, oldCurrentRow);
|
||||
}
|
||||
},
|
||||
|
||||
rowSelectedChanged(states, row) {
|
||||
const changed = toggleRowSelection(states, row);
|
||||
const selection = states.selection;
|
||||
|
||||
if (changed) {
|
||||
const table = this.table;
|
||||
table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
table.$emit('select', selection, row);
|
||||
}
|
||||
|
||||
this.updateAllSelected();
|
||||
},
|
||||
|
||||
toggleAllSelection(state) {
|
||||
this._toggleAllSelection(state);
|
||||
}
|
||||
};
|
||||
|
||||
const doFlattenColumns = (columns) => {
|
||||
const result = [];
|
||||
columns.forEach((column) => {
|
||||
if (column.children) {
|
||||
result.push.apply(result, doFlattenColumns(column.children));
|
||||
} else {
|
||||
result.push(column);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
TableStore.prototype.updateColumns = function() {
|
||||
const states = this.states;
|
||||
const _columns = states._columns || [];
|
||||
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
|
||||
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
|
||||
|
||||
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
|
||||
_columns[0].fixed = true;
|
||||
states.fixedColumns.unshift(_columns[0]);
|
||||
}
|
||||
|
||||
const notFixedColumns = _columns.filter(column => !column.fixed);
|
||||
states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
|
||||
|
||||
const leafColumns = doFlattenColumns(notFixedColumns);
|
||||
const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
|
||||
const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
|
||||
|
||||
states.leafColumnsLength = leafColumns.length;
|
||||
states.fixedLeafColumnsLength = fixedLeafColumns.length;
|
||||
states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
|
||||
|
||||
states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
|
||||
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
|
||||
};
|
||||
|
||||
TableStore.prototype.isSelected = function(row) {
|
||||
return (this.states.selection || []).indexOf(row) > -1;
|
||||
};
|
||||
|
||||
TableStore.prototype.clearSelection = function() {
|
||||
const states = this.states;
|
||||
states.isAllSelected = false;
|
||||
const oldSelection = states.selection;
|
||||
if (states.selection.length) {
|
||||
states.selection = [];
|
||||
}
|
||||
if (oldSelection.length > 0) {
|
||||
this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.setExpandRowKeys = function(rowKeys) {
|
||||
const expandRows = [];
|
||||
const data = this.states.data;
|
||||
const rowKey = this.states.rowKey;
|
||||
if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
|
||||
const keysMap = getKeysMap(data, rowKey);
|
||||
rowKeys.forEach((key) => {
|
||||
const info = keysMap[key];
|
||||
if (info) {
|
||||
expandRows.push(info.row);
|
||||
}
|
||||
});
|
||||
|
||||
this.states.expandRows = expandRows;
|
||||
};
|
||||
|
||||
TableStore.prototype.toggleRowSelection = function(row, selected) {
|
||||
const changed = toggleRowSelection(this.states, row, selected);
|
||||
if (changed) {
|
||||
this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : []);
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.toggleRowExpansion = function(row, expanded) {
|
||||
const changed = toggleRowExpansion(this.states, row, expanded);
|
||||
if (changed) {
|
||||
this.table.$emit('expand-change', row, this.states.expandRows);
|
||||
this.scheduleLayout();
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.isRowExpanded = function(row) {
|
||||
const { expandRows = [], rowKey } = this.states;
|
||||
if (rowKey) {
|
||||
const expandMap = getKeysMap(expandRows, rowKey);
|
||||
return !!expandMap[getRowIdentity(row, rowKey)];
|
||||
}
|
||||
return expandRows.indexOf(row) !== -1;
|
||||
};
|
||||
|
||||
TableStore.prototype.cleanSelection = function() {
|
||||
const selection = this.states.selection || [];
|
||||
const data = this.states.data;
|
||||
const rowKey = this.states.rowKey;
|
||||
let deleted;
|
||||
if (rowKey) {
|
||||
deleted = [];
|
||||
const selectedMap = getKeysMap(selection, rowKey);
|
||||
const dataMap = getKeysMap(data, rowKey);
|
||||
for (let key in selectedMap) {
|
||||
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
|
||||
deleted.push(selectedMap[key].row);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleted = selection.filter((item) => {
|
||||
return data.indexOf(item) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
deleted.forEach((deletedItem) => {
|
||||
selection.splice(selection.indexOf(deletedItem), 1);
|
||||
});
|
||||
|
||||
if (deleted.length) {
|
||||
this.table.$emit('selection-change', selection ? selection.slice() : []);
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.clearFilter = function(columnKeys) {
|
||||
const states = this.states;
|
||||
const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
|
||||
let panels = {};
|
||||
|
||||
if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
|
||||
if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
|
||||
if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
|
||||
|
||||
const keys = Object.keys(panels);
|
||||
if (!keys.length) return;
|
||||
|
||||
if (typeof columnKeys === 'string') {
|
||||
columnKeys = [columnKeys];
|
||||
}
|
||||
if (Array.isArray(columnKeys)) {
|
||||
const columns = columnKeys.map(key => getColumnByKey(states, key));
|
||||
keys.forEach(key => {
|
||||
const column = columns.find(col => col.id === key);
|
||||
if (column) {
|
||||
panels[key].filteredValue = [];
|
||||
}
|
||||
});
|
||||
this.commit('filterChange', {
|
||||
column: columns,
|
||||
value: [],
|
||||
silent: true,
|
||||
multi: true
|
||||
});
|
||||
} else {
|
||||
keys.forEach(key => {
|
||||
panels[key].filteredValue = [];
|
||||
});
|
||||
|
||||
states.filters = {};
|
||||
|
||||
this.commit('filterChange', {
|
||||
column: {},
|
||||
values: [],
|
||||
silent: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.clearSort = function() {
|
||||
const states = this.states;
|
||||
if (!states.sortingColumn) return;
|
||||
states.sortingColumn.order = null;
|
||||
states.sortProp = null;
|
||||
states.sortOrder = null;
|
||||
|
||||
this.commit('changeSortCondition', {
|
||||
silent: true
|
||||
});
|
||||
};
|
||||
|
||||
TableStore.prototype.updateAllSelected = function() {
|
||||
const states = this.states;
|
||||
const { selection, rowKey, selectable, data } = states;
|
||||
if (!data || data.length === 0) {
|
||||
states.isAllSelected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedMap;
|
||||
if (rowKey) {
|
||||
selectedMap = getKeysMap(states.selection, rowKey);
|
||||
}
|
||||
|
||||
const isSelected = function(row) {
|
||||
if (selectedMap) {
|
||||
return !!selectedMap[getRowIdentity(row, rowKey)];
|
||||
} else {
|
||||
return selection.indexOf(row) !== -1;
|
||||
}
|
||||
};
|
||||
|
||||
let isAllSelected = true;
|
||||
let selectedCount = 0;
|
||||
for (let i = 0, j = data.length; i < j; i++) {
|
||||
const item = data[i];
|
||||
const isRowSelectable = selectable && selectable.call(null, item, i);
|
||||
if (!isSelected(item)) {
|
||||
if (!selectable || isRowSelectable) {
|
||||
isAllSelected = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCount === 0) isAllSelected = false;
|
||||
|
||||
states.isAllSelected = isAllSelected;
|
||||
};
|
||||
|
||||
TableStore.prototype.scheduleLayout = function(updateColumns) {
|
||||
if (updateColumns) {
|
||||
this.updateColumns();
|
||||
}
|
||||
this.table.debouncedUpdateLayout();
|
||||
};
|
||||
|
||||
TableStore.prototype.setCurrentRowKey = function(key) {
|
||||
const states = this.states;
|
||||
const rowKey = states.rowKey;
|
||||
if (!rowKey) throw new Error('[Table] row-key should not be empty.');
|
||||
const data = states.data || [];
|
||||
const keysMap = getKeysMap(data, rowKey);
|
||||
const info = keysMap[key];
|
||||
states.currentRow = info ? info.row : null;
|
||||
};
|
||||
|
||||
TableStore.prototype.updateCurrentRow = function() {
|
||||
const states = this.states;
|
||||
const table = this.table;
|
||||
const data = states.data || [];
|
||||
const oldCurrentRow = states.currentRow;
|
||||
|
||||
if (data.indexOf(oldCurrentRow) === -1) {
|
||||
if (states.rowKey && oldCurrentRow) {
|
||||
let newCurrentRow = null;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i];
|
||||
if (item && item[states.rowKey] === oldCurrentRow[states.rowKey]) {
|
||||
newCurrentRow = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newCurrentRow) {
|
||||
states.currentRow = newCurrentRow;
|
||||
return;
|
||||
}
|
||||
}
|
||||
states.currentRow = null;
|
||||
|
||||
if (states.currentRow !== oldCurrentRow) {
|
||||
table.$emit('current-change', null, oldCurrentRow);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TableStore.prototype.commit = function(name, ...args) {
|
||||
const mutations = this.mutations;
|
||||
if (mutations[name]) {
|
||||
mutations[name].apply(this, [this.states].concat(args));
|
||||
} else {
|
||||
throw new Error(`Action not found: ${name}`);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
|
@ -214,36 +214,17 @@
|
|||
|
||||
<script type="text/babel">
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import { debounce, throttle } from 'throttle-debounce';
|
||||
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
|
||||
import Mousewheel from 'element-ui/src/directives/mousewheel';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import Migrating from 'element-ui/src/mixins/migrating';
|
||||
import TableStore from './table-store';
|
||||
import { createStore, mapStates } from './store/helper';
|
||||
import TableLayout from './table-layout';
|
||||
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;
|
||||
}
|
||||
};
|
||||
import { parseHeight } from './util';
|
||||
|
||||
let tableIdSeed = 1;
|
||||
|
||||
|
@ -338,6 +319,16 @@
|
|||
default: 16
|
||||
},
|
||||
|
||||
treeProps: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
hasChildren: 'hasChildren',
|
||||
children: 'children'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
lazy: Boolean,
|
||||
|
||||
load: Function
|
||||
|
@ -369,7 +360,7 @@
|
|||
},
|
||||
|
||||
toggleRowExpansion(row, expanded) {
|
||||
this.store.toggleRowExpansion(row, expanded);
|
||||
this.store.toggleRowExpansionAdapter(row, expanded);
|
||||
},
|
||||
|
||||
clearSelection() {
|
||||
|
@ -413,37 +404,42 @@
|
|||
handleHeaderFooterMousewheel(event, data) {
|
||||
const { pixelX, pixelY } = data;
|
||||
if (Math.abs(pixelX) >= Math.abs(pixelY)) {
|
||||
event.preventDefault();
|
||||
this.bodyWrapper.scrollLeft += data.pixelX / 5;
|
||||
}
|
||||
},
|
||||
|
||||
bindEvents() {
|
||||
const { headerWrapper, footerWrapper } = this.$refs;
|
||||
const refs = this.$refs;
|
||||
let self = this;
|
||||
|
||||
this.bodyWrapper.addEventListener('scroll', function() {
|
||||
if (headerWrapper) headerWrapper.scrollLeft = this.scrollLeft;
|
||||
if (footerWrapper) footerWrapper.scrollLeft = this.scrollLeft;
|
||||
if (refs.fixedBodyWrapper) refs.fixedBodyWrapper.scrollTop = this.scrollTop;
|
||||
if (refs.rightFixedBodyWrapper) refs.rightFixedBodyWrapper.scrollTop = this.scrollTop;
|
||||
const maxScrollLeftPosition = this.scrollWidth - this.offsetWidth - 1;
|
||||
const scrollLeft = this.scrollLeft;
|
||||
// TODO 性能优化
|
||||
syncPostion: throttle(20, function() {
|
||||
const { scrollLeft, scrollTop, offsetWidth, scrollWidth } = this.bodyWrapper;
|
||||
const { headerWrapper, footerWrapper, fixedBodyWrapper, rightFixedBodyWrapper } = this.$refs;
|
||||
if (headerWrapper) headerWrapper.scrollLeft = scrollLeft;
|
||||
if (footerWrapper) footerWrapper.scrollLeft = scrollLeft;
|
||||
if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop;
|
||||
if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop;
|
||||
const maxScrollLeftPosition = scrollWidth - offsetWidth - 1;
|
||||
if (scrollLeft >= maxScrollLeftPosition) {
|
||||
self.scrollPosition = 'right';
|
||||
this.scrollPosition = 'right';
|
||||
} else if (scrollLeft === 0) {
|
||||
self.scrollPosition = 'left';
|
||||
this.scrollPosition = 'left';
|
||||
} else {
|
||||
self.scrollPosition = 'middle';
|
||||
this.scrollPosition = 'middle';
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
bindEvents() {
|
||||
this.bodyWrapper.addEventListener('scroll', this.syncPostion, { passive: true });
|
||||
if (this.fit) {
|
||||
addResizeListener(this.$el, this.resizeListener);
|
||||
}
|
||||
},
|
||||
|
||||
unbindEvents() {
|
||||
this.bodyWrapper.removeEventListener('scroll', this.syncPostion, { passive: true });
|
||||
if (this.fit) {
|
||||
removeResizeListener(this.$el, this.resizeListener);
|
||||
}
|
||||
},
|
||||
|
||||
resizeListener() {
|
||||
if (!this.$ready) return;
|
||||
let shouldUpdateLayout = false;
|
||||
|
@ -480,61 +476,8 @@
|
|||
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.tableId = 'el-table_' + tableIdSeed++;
|
||||
this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -553,41 +496,25 @@
|
|||
this.rightFixedColumns.length > 0;
|
||||
},
|
||||
|
||||
selection() {
|
||||
return this.store.states.selection;
|
||||
},
|
||||
|
||||
columns() {
|
||||
return this.store.states.columns;
|
||||
},
|
||||
|
||||
tableData() {
|
||||
return this.store.states.data;
|
||||
},
|
||||
|
||||
fixedColumns() {
|
||||
return this.store.states.fixedColumns;
|
||||
},
|
||||
|
||||
rightFixedColumns() {
|
||||
return this.store.states.rightFixedColumns;
|
||||
},
|
||||
|
||||
bodyWidth() {
|
||||
const { bodyWidth, scrollY, gutterWidth } = this.layout;
|
||||
return bodyWidth ? bodyWidth - (scrollY ? gutterWidth : 0) + 'px' : '';
|
||||
},
|
||||
|
||||
bodyHeight() {
|
||||
const { headerHeight = 0, bodyHeight, footerHeight = 0} = this.layout;
|
||||
if (this.height) {
|
||||
return {
|
||||
height: this.layout.bodyHeight ? this.layout.bodyHeight + 'px' : ''
|
||||
height: bodyHeight ? bodyHeight + 'px' : ''
|
||||
};
|
||||
} else if (this.maxHeight) {
|
||||
const maxHeight = parseHeight(this.maxHeight);
|
||||
if (maxHeight) {
|
||||
return {
|
||||
'max-height': this.layout.bodyHeight ? this.layout.bodyHeight + 'px' : ''
|
||||
'max-height': (maxHeight - footerHeight - (this.showHeader ? headerHeight : 0)) + 'px'
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
|
@ -597,19 +524,18 @@
|
|||
height: this.layout.fixedBodyHeight ? this.layout.fixedBodyHeight + 'px' : ''
|
||||
};
|
||||
} else if (this.maxHeight) {
|
||||
let maxHeight = this.layout.scrollX ? this.maxHeight - this.layout.gutterWidth : this.maxHeight;
|
||||
|
||||
let maxHeight = parseHeight(this.maxHeight);
|
||||
if (maxHeight) {
|
||||
maxHeight = this.layout.scrollX ? maxHeight - this.layout.gutterWidth : maxHeight;
|
||||
if (this.showHeader) {
|
||||
maxHeight -= this.layout.headerHeight;
|
||||
}
|
||||
|
||||
maxHeight -= this.layout.footerHeight;
|
||||
|
||||
return {
|
||||
'max-height': maxHeight + 'px'
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
|
@ -633,7 +559,15 @@
|
|||
height: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
...mapStates({
|
||||
selection: 'selection',
|
||||
columns: 'columns',
|
||||
tableData: 'data',
|
||||
fixedColumns: 'fixedColumns',
|
||||
rightFixedColumns: 'rightFixedColumns'
|
||||
})
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -658,8 +592,6 @@
|
|||
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(() => {
|
||||
|
@ -673,14 +605,15 @@
|
|||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.store.setExpandRowKeys(newVal);
|
||||
this.store.setExpandRowKeysAdapter(newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if (this.resizeListener) removeResizeListener(this.$el, this.resizeListener);
|
||||
created() {
|
||||
this.tableId = 'el-table_' + tableIdSeed++;
|
||||
this.debouncedUpdateLayout = debounce(50, () => this.doLayout());
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -707,23 +640,30 @@
|
|||
this.$ready = true;
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.unbindEvents();
|
||||
},
|
||||
|
||||
data() {
|
||||
const store = new TableStore(this, {
|
||||
const { hasChildren = 'hasChildren', children = 'children' } = this.treeProps;
|
||||
this.store = createStore(this, {
|
||||
rowKey: this.rowKey,
|
||||
defaultExpandAll: this.defaultExpandAll,
|
||||
selectOnIndeterminate: this.selectOnIndeterminate,
|
||||
// TreeTable 的相关配置
|
||||
indent: this.indent,
|
||||
lazy: this.lazy
|
||||
lazy: this.lazy,
|
||||
lazyColumnIdentifier: hasChildren,
|
||||
childrenColumnName: children
|
||||
});
|
||||
const layout = new TableLayout({
|
||||
store,
|
||||
store: this.store,
|
||||
table: this,
|
||||
fit: this.fit,
|
||||
showHeader: this.showHeader
|
||||
});
|
||||
return {
|
||||
layout,
|
||||
store,
|
||||
isHidden: false,
|
||||
renderExpanded: null,
|
||||
resizeProxyVisible: false,
|
||||
|
|
|
@ -120,3 +120,135 @@ export const getRowIdentity = (row, rowKey) => {
|
|||
return rowKey.call(null, row);
|
||||
}
|
||||
};
|
||||
|
||||
export const getKeysMap = function(array, rowKey) {
|
||||
const arrayMap = {};
|
||||
(array || []).forEach((row, index) => {
|
||||
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
|
||||
});
|
||||
return arrayMap;
|
||||
};
|
||||
|
||||
function hasOwn(obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
}
|
||||
|
||||
export function mergeOptions(defaults, config) {
|
||||
const options = {};
|
||||
let key;
|
||||
for (key in defaults) {
|
||||
options[key] = defaults[key];
|
||||
}
|
||||
for (key in config) {
|
||||
if (hasOwn(config, key)) {
|
||||
const value = config[key];
|
||||
if (typeof value !== 'undefined') {
|
||||
options[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export function parseWidth(width) {
|
||||
if (width !== undefined) {
|
||||
width = parseInt(width, 10);
|
||||
if (isNaN(width)) {
|
||||
width = null;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
export function parseMinWidth(minWidth) {
|
||||
if (typeof minWidth !== 'undefined') {
|
||||
minWidth = parseWidth(minWidth);
|
||||
if (isNaN(minWidth)) {
|
||||
minWidth = 80;
|
||||
}
|
||||
}
|
||||
return minWidth;
|
||||
};
|
||||
|
||||
export function parseHeight(height) {
|
||||
if (typeof height === 'number') {
|
||||
return height;
|
||||
}
|
||||
if (typeof height === 'string') {
|
||||
if (/^\d+(?:px)?/.test(height)) {
|
||||
return parseInt(height, 10);
|
||||
}
|
||||
console.warn('[Element Warn][ElTable]invalid height and it will be ignored.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://github.com/reduxjs/redux/blob/master/src/compose.js
|
||||
export function compose(...funcs) {
|
||||
if (funcs.length === 0) {
|
||||
return arg => arg;
|
||||
}
|
||||
if (funcs.length === 1) {
|
||||
return funcs[0];
|
||||
}
|
||||
return funcs.reduce((a, b) => (...args) => a(b(...args)));
|
||||
}
|
||||
|
||||
export function toggleRowStatus(statusArr, row, newVal) {
|
||||
let changed = false;
|
||||
const index = statusArr.indexOf(row);
|
||||
const included = index !== -1;
|
||||
|
||||
const addRow = () => {
|
||||
statusArr.push(row);
|
||||
changed = true;
|
||||
};
|
||||
const removeRow = () => {
|
||||
statusArr.splice(index, 1);
|
||||
changed = true;
|
||||
};
|
||||
|
||||
if (typeof newVal === 'boolean') {
|
||||
if (newVal && !included) {
|
||||
addRow();
|
||||
} else if (!newVal && included) {
|
||||
removeRow();
|
||||
}
|
||||
} else {
|
||||
if (included) {
|
||||
removeRow();
|
||||
} else {
|
||||
addRow();
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function walkTreeNode(root, cb, childrenKey = 'children', lazyKey = 'hasChildren') {
|
||||
const isNil = (array) => !(Array.isArray(array) && array.length);
|
||||
|
||||
function _walker(parent, children, level) {
|
||||
cb(parent, children, level);
|
||||
children.forEach(item => {
|
||||
if (item[lazyKey]) {
|
||||
cb(item, null, level + 1);
|
||||
return;
|
||||
}
|
||||
const children = item[childrenKey];
|
||||
if (!isNil(children)) {
|
||||
_walker(item, children, level + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
root.forEach(item => {
|
||||
if (item[lazyKey]) {
|
||||
cb(item, null, 0);
|
||||
return;
|
||||
}
|
||||
const children = item[childrenKey];
|
||||
if (!isNil(children)) {
|
||||
_walker(item, children, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -561,9 +561,11 @@
|
|||
[class*=el-table__row--level] {
|
||||
.el-table__expand-icon {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createVue, triggerEvent, destroyVM, waitImmediate } from '../util';
|
||||
import { createVue, triggerEvent, destroyVM, waitImmediate, wait } from '../util';
|
||||
|
||||
const DELAY = 10;
|
||||
const testDataArr = [];
|
||||
|
@ -91,9 +91,9 @@ describe('Table', () => {
|
|||
});
|
||||
|
||||
it('height as string', done => {
|
||||
const vm = createTable('height="100pt"');
|
||||
const vm = createTable('height="100px"');
|
||||
setTimeout(_ => {
|
||||
expect(vm.$el.style.height).to.equal('100pt');
|
||||
expect(vm.$el.style.height).to.equal('100px');
|
||||
destroyVM(vm);
|
||||
done();
|
||||
}, DELAY);
|
||||
|
@ -1760,7 +1760,7 @@ describe('Table', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('hover', done => {
|
||||
it('hover', async() => {
|
||||
const vm = createVue({
|
||||
template: `
|
||||
<el-table :data="testData">
|
||||
|
@ -1776,19 +1776,17 @@ describe('Table', () => {
|
|||
};
|
||||
}
|
||||
}, true);
|
||||
setTimeout(_ => {
|
||||
await waitImmediate();
|
||||
const tr = vm.$el.querySelector('.el-table__body-wrapper tbody tr');
|
||||
triggerEvent(tr, 'mouseenter', true, false);
|
||||
setTimeout(_ => {
|
||||
|
||||
await wait(50);
|
||||
expect(tr.classList.contains('hover-row')).to.true;
|
||||
triggerEvent(tr, 'mouseleave', true, false);
|
||||
setTimeout(_ => {
|
||||
|
||||
await wait(50);
|
||||
expect(tr.classList.contains('hover-row')).to.false;
|
||||
destroyVM(vm);
|
||||
done();
|
||||
}, DELAY);
|
||||
}, DELAY);
|
||||
}, DELAY);
|
||||
});
|
||||
|
||||
it('highlight-current-row', done => {
|
||||
|
@ -1910,8 +1908,11 @@ describe('Table', () => {
|
|||
}, DELAY);
|
||||
});
|
||||
|
||||
it('render tree structual data', (done) => {
|
||||
const vm = createVue({
|
||||
describe('tree', () => {
|
||||
let vm;
|
||||
afterEach(() => destroyVM(vm));
|
||||
it('render tree structual data', async() => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-table :data="testData" row-key="release">
|
||||
<el-table-column prop="name" label="片名" />
|
||||
|
@ -1935,29 +1936,25 @@ describe('Table', () => {
|
|||
};
|
||||
}
|
||||
}, true);
|
||||
setTimeout(() => {
|
||||
await waitImmediate();
|
||||
|
||||
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(() => {
|
||||
await waitImmediate();
|
||||
childRows.forEach(item => {
|
||||
expect(item.style.display).to.equal('');
|
||||
});
|
||||
done();
|
||||
}, DELAY);
|
||||
}, DELAY);
|
||||
});
|
||||
|
||||
it('load substree row data', async() => {
|
||||
const vm = createVue({
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-table :data="testData" row-key="release" lazy :load="load">
|
||||
<el-table-column prop="name" label="片名" />
|
||||
|
@ -1991,7 +1988,6 @@ describe('Table', () => {
|
|||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
await waitImmediate();
|
||||
|
||||
const expandIcon = vm.$el.querySelector('.el-table__expand-icon');
|
||||
|
@ -2002,4 +1998,114 @@ describe('Table', () => {
|
|||
expect(expandIcon.classList.contains('el-table__expand-icon--expanded')).to.be.true;
|
||||
expect(vm.$el.querySelectorAll('.el-table__row').length).to.equal(8);
|
||||
});
|
||||
|
||||
it('tree-props & default-expand-all & expand-change', async() => {
|
||||
const spy = sinon.spy();
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-table
|
||||
:data="testData" lazy default-expand-all row-key="release" :tree-props="{children: 'childrenTest', hasChildren: 'hasChildrenTest'}"
|
||||
:load="load" @expand-change="change">
|
||||
<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[testData.length - 1].childrenTest = [
|
||||
{
|
||||
name: 'A Bug\'s Life copy 1', release: '2008-1-25-1', director: 'John Lasseter', runtime: 95
|
||||
}
|
||||
];
|
||||
testData[1].hasChildrenTest = 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
|
||||
}
|
||||
]);
|
||||
},
|
||||
change: spy
|
||||
}
|
||||
}, true);
|
||||
await waitImmediate();
|
||||
const childRows = vm.$el.querySelectorAll('.el-table__row--level-1');
|
||||
childRows.forEach(item => {
|
||||
expect(item.style.display).to.equal('');
|
||||
});
|
||||
const expandIcon = vm.$el.querySelector('.el-table__expand-icon');
|
||||
expandIcon.click();
|
||||
|
||||
await waitImmediate();
|
||||
|
||||
expect(expandIcon.classList.contains('el-table__expand-icon--expanded')).to.be.true;
|
||||
expect(vm.$el.querySelectorAll('.el-table__row').length).to.equal(8);
|
||||
expect(spy.args[0][0]).to.be.an('object');
|
||||
expect(spy.args[0][1]).to.be.true;
|
||||
});
|
||||
|
||||
it('expand-row-keys & toggleRowExpansion', async() => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-table :data="testData" row-key="release" lazy :load="load" :expand-row-keys="['2003-5-30']" ref="table">
|
||||
<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[testData.length - 1].children = [
|
||||
{
|
||||
name: 'A Bug\'s Life copy 1', release: '2003-5-30-1', director: 'John Lasseter', runtime: 95,
|
||||
hasChildren: true
|
||||
}
|
||||
];
|
||||
return {
|
||||
testData: testData
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
load(row, treeNode, resolve) {
|
||||
resolve([
|
||||
{
|
||||
name: 'A Bug\'s Life copy 1', release: '2003-5-30-2', director: 'John Lasseter', runtime: 95
|
||||
}
|
||||
]);
|
||||
},
|
||||
closeExpandRow() {
|
||||
const testData = this.testData;
|
||||
const row = testData[testData.length - 1].children[0];
|
||||
this.$refs.table.toggleRowExpansion(row);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
await waitImmediate();
|
||||
const childRows = vm.$el.querySelectorAll('.el-table__row--level-1');
|
||||
childRows.forEach(item => {
|
||||
expect(item.style.display).to.equal('');
|
||||
});
|
||||
const expandIcon = childRows[0].querySelector('.el-table__expand-icon');
|
||||
expandIcon.click();
|
||||
|
||||
await waitImmediate();
|
||||
|
||||
expect(expandIcon.classList.contains('el-table__expand-icon--expanded')).to.be.true;
|
||||
vm.closeExpandRow();
|
||||
|
||||
await waitImmediate();
|
||||
expect(expandIcon.classList.contains('el-table__expand-icon--expanded')).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue