From 04ed04effd6a086265d6cbf0b2ce550069f10ece Mon Sep 17 00:00:00 2001 From: msidolphin Date: Thu, 10 Feb 2022 09:35:29 +0800 Subject: [PATCH] feat(components): [el-table] support tableLayout (#5860) * feat(components): [el-table] support tableLayout * fix: typo * fix: remove unused var * fix: optimize code * fix: typo --- docs/en-US/component/table.md | 11 + docs/examples/table/table-layout.vue | 40 ++++ .../components/table/__tests__/table.spec.ts | 25 +++ packages/components/table/src/h-helper.ts | 41 +++- .../components/table/src/table-body/index.ts | 26 +-- .../table/src/table-header/index.ts | 188 ++++++++---------- packages/components/table/src/table-layout.ts | 14 +- packages/components/table/src/table.vue | 67 +++++-- .../components/table/src/table/defaults.ts | 7 +- .../table/src/table/style-helper.ts | 6 + 10 files changed, 269 insertions(+), 156 deletions(-) create mode 100644 docs/examples/table/table-layout.vue diff --git a/docs/en-US/component/table.md b/docs/en-US/component/table.md index 81d655c329..3a0141c05d 100644 --- a/docs/en-US/component/table.md +++ b/docs/en-US/component/table.md @@ -203,6 +203,16 @@ table/custom-index ::: +## Table Layout + +The [table-layout](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout) property sets the algorithm used to lay out table cells, rows, and columns. + +:::demo + +table/table-layout + +::: + ## Table Attributes | Attribute | Description | Type | Accepted Values | Default | @@ -240,6 +250,7 @@ table/custom-index | 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' }` | +| table-layout | Sets the algorithm used to lay out table cells, rows, and columns | string | fixed / auto | fixed | ## Table Events diff --git a/docs/examples/table/table-layout.vue b/docs/examples/table/table-layout.vue new file mode 100644 index 0000000000..0e4724a352 --- /dev/null +++ b/docs/examples/table/table-layout.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/components/table/__tests__/table.spec.ts b/packages/components/table/__tests__/table.spec.ts index 8e3af7076b..e77e8430f1 100644 --- a/packages/components/table/__tests__/table.spec.ts +++ b/packages/components/table/__tests__/table.spec.ts @@ -1356,4 +1356,29 @@ describe('Table.vue', () => { expect(firstCellSpanAfterHide.classes().includes('release')).toBeTruthy() }) }) + + it('when tableLayout is auto', async () => { + const wrapper = mount({ + components: { + ElTable, + ElTableColumn, + }, + template: ` + + + + + + + + `, + created() { + this.testData = getTestData() + }, + }) + await nextTick() + expect(wrapper.find('.el-table__body thead').exists()).toBeTruthy() + expect(wrapper.find('.el-table__body colgroup col').exists()).toBeFalsy() + expect(wrapper.find('.el-table__body tbody').exists()).toBeTruthy() + }) }) diff --git a/packages/components/table/src/h-helper.ts b/packages/components/table/src/h-helper.ts index 0fea1dac39..a37716cb8d 100644 --- a/packages/components/table/src/h-helper.ts +++ b/packages/components/table/src/h-helper.ts @@ -1,14 +1,33 @@ import { h } from 'vue' +export function hColgroup(props) { + const isAuto = props.tableLayout === 'auto' + let columns = props.columns || [] + if (isAuto) { + if (columns.every((column) => column.width === undefined)) { + columns = [] + } + } + const getPropsData = (column) => { + const propsData = { + key: `${props.tableLayout}_${column.id}`, + style: {}, + name: undefined, + } + if (isAuto) { + propsData.style = { + width: `${column.width}px`, + } + } else { + propsData.name = column.id + } + return propsData + } -import type { TableColumnCtx } from './table-column/defaults' - -export function hColgroup(columns: TableColumnCtx[]) { - return h('colgroup', {}, [ - ...columns.map((column) => - h('col', { - name: column.id, - key: column.id, - }) - ), - ]) + return h( + 'colgroup', + {}, + columns.map((column) => h('col', getPropsData(column))) + ) } + +hColgroup.props = ['columns', 'tableLayout'] diff --git a/packages/components/table/src/table-body/index.ts b/packages/components/table/src/table-body/index.ts index 33643a1087..bb032a1948 100644 --- a/packages/components/table/src/table-body/index.ts +++ b/packages/components/table/src/table-body/index.ts @@ -10,7 +10,6 @@ import { import { isClient } from '@vueuse/core' import { addClass, removeClass } from '@element-plus/utils/dom' import { useNamespace } from '@element-plus/hooks' -import { hColgroup } from '../h-helper' import useLayoutObserver from '../layout-observer' import { removePopper } from '../util' import { TABLE_INJECTION_KEY } from '../tokens' @@ -66,25 +65,12 @@ export default defineComponent({ } }, render() { - const { ns, wrappedRowRender, store } = this + const { wrappedRowRender, store } = this const data = store.states.data.value || [] - const columns = store.states.columns.value - return h( - 'table', - { - class: ns.e('body'), - cellspacing: '0', - cellpadding: '0', - border: '0', - }, - [ - hColgroup(columns), - h('tbody', {}, [ - data.reduce((acc: VNode[], row) => { - return acc.concat(wrappedRowRender(row, acc.length)) - }, []), - ]), - ] - ) + return h('tbody', {}, [ + data.reduce((acc: VNode[], row) => { + return acc.concat(wrappedRowRender(row, acc.length)) + }, []), + ]) }, }) diff --git a/packages/components/table/src/table-header/index.ts b/packages/components/table/src/table-header/index.ts index d0d229848f..19562c7391 100644 --- a/packages/components/table/src/table-header/index.ts +++ b/packages/components/table/src/table-header/index.ts @@ -11,7 +11,6 @@ import ElCheckbox from '@element-plus/components/checkbox' import { useNamespace } from '@element-plus/hooks' import FilterPanel from '../filter-panel.vue' import useLayoutObserver from '../layout-observer' -import { hColgroup } from '../h-helper' import { TABLE_INJECTION_KEY } from '../tokens' import useEvent from './event-helper' import useStyle from './style.helper' @@ -62,7 +61,6 @@ export default defineComponent({ const instance = getCurrentInstance() as TableHeader const parent = inject(TABLE_INJECTION_KEY) const ns = useNamespace('table') - const storeData = parent?.store.states const filterPanels = ref({}) const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent!) onMounted(() => { @@ -100,7 +98,6 @@ export default defineComponent({ return { ns, - columns: storeData.columns, filterPanels, onColumnsChange, onScrollableChange, @@ -123,7 +120,6 @@ export default defineComponent({ render() { const { ns, - columns, isGroup, columnRows, getHeaderCellStyle, @@ -141,118 +137,104 @@ export default defineComponent({ } = this let rowSpan = 1 return h( - 'table', + 'thead', { - border: '0', - cellpadding: '0', - cellspacing: '0', - class: ns.e('header'), + class: { [ns.is('group')]: isGroup }, }, - [ - hColgroup(columns), + columnRows.map((subColumns, rowIndex) => h( - 'thead', + 'tr', { - class: { [ns.is('group')]: isGroup }, + class: getHeaderRowClass(rowIndex), + key: rowIndex, + style: getHeaderRowStyle(rowIndex), }, - columnRows.map((subColumns, rowIndex) => - h( - 'tr', + subColumns.map((column, cellIndex) => { + if (column.rowSpan > rowSpan) { + rowSpan = column.rowSpan + } + return h( + 'th', { - class: getHeaderRowClass(rowIndex), - key: rowIndex, - style: getHeaderRowStyle(rowIndex), + class: getHeaderCellClass( + rowIndex, + cellIndex, + subColumns, + column + ), + colspan: column.colSpan, + key: `${column.id}-thead`, + rowspan: column.rowSpan, + style: getHeaderCellStyle( + rowIndex, + cellIndex, + subColumns, + column + ), + onClick: ($event) => handleHeaderClick($event, column), + onContextmenu: ($event) => + handleHeaderContextMenu($event, column), + onMousedown: ($event) => handleMouseDown($event, column), + onMousemove: ($event) => handleMouseMove($event, column), + onMouseout: handleMouseOut, }, - subColumns.map((column, cellIndex) => { - if (column.rowSpan > rowSpan) { - rowSpan = column.rowSpan - } - return h( - 'th', + [ + h( + 'div', { - class: getHeaderCellClass( - rowIndex, - cellIndex, - subColumns, - column - ), - colspan: column.colSpan, - key: `${column.id}-thead`, - rowSpan: column.rowSpan, - style: getHeaderCellStyle( - rowIndex, - cellIndex, - subColumns, - column - ), - onClick: ($event) => handleHeaderClick($event, column), - onContextmenu: ($event) => - handleHeaderContextMenu($event, column), - onMousedown: ($event) => handleMouseDown($event, column), - onMousemove: ($event) => handleMouseMove($event, column), - onMouseout: handleMouseOut, + class: [ + 'cell', + column.filteredValue && column.filteredValue.length > 0 + ? 'highlight' + : '', + column.labelClassName, + ], }, [ - h( - 'div', - { - class: [ - 'cell', - column.filteredValue && - column.filteredValue.length > 0 - ? 'highlight' - : '', - column.labelClassName, - ], - }, - [ - column.renderHeader - ? column.renderHeader({ - column, - $index: cellIndex, - store, - _self: $parent, - }) - : column.label, - column.sortable && - h( - 'span', - { - onClick: ($event) => - handleSortClick($event, column), - class: 'caret-wrapper', - }, - [ - h('i', { - onClick: ($event) => - handleSortClick($event, column, 'ascending'), - class: 'sort-caret ascending', - }), - h('i', { - onClick: ($event) => - handleSortClick($event, column, 'descending'), - class: 'sort-caret descending', - }), - ] - ), - column.filterable && - h(FilterPanel, { - store: $parent.store, - placement: column.filterPlacement || 'bottom-start', - column, - upDataColumn: (key, value) => { - column[key] = value - }, + column.renderHeader + ? column.renderHeader({ + column, + $index: cellIndex, + store, + _self: $parent, + }) + : column.label, + column.sortable && + h( + 'span', + { + onClick: ($event) => handleSortClick($event, column), + class: 'caret-wrapper', + }, + [ + h('i', { + onClick: ($event) => + handleSortClick($event, column, 'ascending'), + class: 'sort-caret ascending', }), - ] - ), + h('i', { + onClick: ($event) => + handleSortClick($event, column, 'descending'), + class: 'sort-caret descending', + }), + ] + ), + column.filterable && + h(FilterPanel, { + store: $parent.store, + placement: column.filterPlacement || 'bottom-start', + column, + upDataColumn: (key, value) => { + column[key] = value + }, + }), ] - ) - }) + ), + ] ) - ) - ), - ] + }) + ) + ) ) }, }) diff --git a/packages/components/table/src/table-layout.ts b/packages/components/table/src/table-layout.ts index 4880e6dc16..f1d7f56045 100644 --- a/packages/components/table/src/table-layout.ts +++ b/packages/components/table/src/table-layout.ts @@ -145,7 +145,7 @@ class TableLayout { } = this.table.refs this.appendHeight.value = appendWrapper ? appendWrapper.offsetHeight : 0 if (this.showHeader && !headerWrapper) return - const headerTrElm: HTMLElement = tableHeader ? tableHeader.$el : null + const headerTrElm: HTMLElement = tableHeader ? tableHeader : null const noneHeader = this.headerDisplayNone(headerTrElm) const headerHeight = (this.headerHeight.value = !this.showHeader @@ -171,7 +171,7 @@ class TableLayout { } this.bodyHeight.value = tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0) - this.bodyScrollHeight.value = tableBody?.$el.scrollHeight! + this.bodyScrollHeight.value = tableBody?.scrollHeight } this.fixedBodyHeight.value = this.scrollX.value ? this.bodyHeight.value - this.gutterWidth @@ -201,6 +201,8 @@ class TableLayout { if (!isClient) return const fit = this.fit const bodyWidth = this.table.vnode.el.clientWidth + const { tableBody } = this.table.refs + const bodyScrollWidth = tableBody?.scrollWidth || 0 let bodyMinWidth = 0 const flattenColumns = this.getFlattenColumns() @@ -217,9 +219,11 @@ class TableLayout { bodyMinWidth += Number(column.width || column.minWidth || 80) }) - const scrollYWidth = this.scrollY.value ? this.gutterWidth : 0 - - if (bodyMinWidth <= bodyWidth - scrollYWidth) { + const scrollYWidth = 0 + if ( + bodyMinWidth <= bodyWidth - scrollYWidth && + bodyScrollWidth <= bodyWidth + ) { // DON'T HAVE SCROLL BAR this.scrollX.value = false diff --git a/packages/components/table/src/table.vue b/packages/components/table/src/table.vue index 2d09dc2789..ddc9ab23f5 100644 --- a/packages/components/table/src/table.vue +++ b/packages/components/table/src/table.vue @@ -20,6 +20,7 @@ ns.m(tableSize), className, ns.b(), + ns.m(`layout-${tableLayout}`), ]" :style="style" :data-prefix="ns.namespace.value" @@ -30,19 +31,30 @@
- + border="0" + cellpadding="0" + cellspacing="0" + > + + +
- + > + + + +
(props, layout, store, table) const debouncedUpdateLayout = debounce(doLayout, 50) @@ -266,6 +300,7 @@ export default defineComponent({ context: table, computedSumText, computedEmptyText, + tableLayout, } }, }) diff --git a/packages/components/table/src/table/defaults.ts b/packages/components/table/src/table/defaults.ts index 1fce463ef3..ad81d7a430 100644 --- a/packages/components/table/src/table/defaults.ts +++ b/packages/components/table/src/table/defaults.ts @@ -77,7 +77,7 @@ type CellStyle = column: TableColumnCtx columnIndex: number }) => CSSProperties) - +type Layout = 'fixed' | 'auto' interface TableProps { data: T[] size?: string @@ -129,6 +129,7 @@ interface TableProps { load?: (row: T, treeNode: TreeNode, resolve: (data: T[]) => void) => void className?: string style?: CSSProperties + tableLayout: Layout } interface Sort { @@ -244,6 +245,10 @@ export default { type: String, default: '', }, + tableLayout: { + type: String as PropType, + default: 'fixed', + }, } export type { SummaryMethod, diff --git a/packages/components/table/src/table/style-helper.ts b/packages/components/table/src/table/style-helper.ts index 8c618a03d6..424a3f047e 100644 --- a/packages/components/table/src/table/style-helper.ts +++ b/packages/components/table/src/table/style-helper.ts @@ -218,6 +218,11 @@ function useStyle( : '' }) + const tableLayout = computed(() => { + if (props.maxHeight) return 'fixed' + return props.tableLayout + }) + const height = computed(() => { const headerHeight = layout.headerHeight.value || 0 const bodyHeight = layout.bodyHeight.value @@ -355,6 +360,7 @@ function useStyle( resizeState, doLayout, tableBodyStyles, + tableLayout, } }