refactor: [el-table] refactoring table with el-scrollbar (#5384)

* refactor: [el-table] refactoring table with el-scrollbar

* refactor: remove gutter

* fix: remove unused

* fix: optimize code
This commit is contained in:
msidolphin 2022-01-19 17:12:58 +08:00 committed by GitHub
parent d05acba3d0
commit 13be920c9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 233 additions and 336 deletions

View File

@ -2,13 +2,7 @@ import { h } from 'vue'
import type { TableColumnCtx } from './table-column/defaults'
export function hGutter() {
return h('col', {
name: 'gutter',
})
}
export function hColgroup<T>(columns: TableColumnCtx<T>[], hasGutter = false) {
export function hColgroup<T>(columns: TableColumnCtx<T>[]) {
return h('colgroup', {}, [
...columns.map((column) =>
h('col', {
@ -16,6 +10,5 @@ export function hColgroup<T>(columns: TableColumnCtx<T>[], hasGutter = false) {
key: column.id,
})
),
hasGutter && hGutter(),
])
}

View File

@ -53,12 +53,13 @@ function useLayoutObserver<T>(root: Table<T>) {
}
const onScrollableChange = (layout: TableLayout<T>) => {
const cols = root.vnode.el.querySelectorAll('colgroup > col[name=gutter]')
const cols =
root.vnode.el?.querySelectorAll('colgroup > col[name=gutter]') || []
for (let i = 0, j = cols.length; i < j; i++) {
const col = cols[i]
col.setAttribute('width', layout.scrollY.value ? layout.gutterWidth : '0')
}
const ths = root.vnode.el.querySelectorAll('th.gutter')
const ths = root.vnode.el?.querySelectorAll('th.gutter') || []
for (let i = 0, j = ths.length; i < j; i++) {
const th = ths[i]
th.style.width = layout.scrollY.value ? `${layout.gutterWidth}px` : '0'

View File

@ -1,15 +1,13 @@
import { getCurrentInstance, ref, h } from 'vue'
import { ref, h, inject } from 'vue'
import debounce from 'lodash/debounce'
import { getStyle, hasClass } from '@element-plus/utils/dom'
import { createTablePopper, getCell, getColumnByCell } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
import type { TableBodyProps } from './defaults'
function useEvents<T>(props: Partial<TableBodyProps<T>>) {
const instance = getCurrentInstance()
const parent = instance.parent as Table<T>
const parent = inject(TABLE_INJECTION_KEY)
const tooltipContent = ref('')
const tooltipTrigger = ref(h('div'))
const handleEvent = (event: Event, row: T, name: string) => {

View File

@ -5,25 +5,25 @@ import {
watch,
onUnmounted,
onUpdated,
inject,
} from 'vue'
import { isClient } from '@vueuse/core'
import { addClass, removeClass } from '@element-plus/utils/dom'
import { hColgroup } from '../h-helper'
import useLayoutObserver from '../layout-observer'
import { removePopper } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import useRender from './render-helper'
import defaultProps from './defaults'
import type { VNode } from 'vue'
import type { DefaultRow, Table } from '../table/defaults'
export default defineComponent({
name: 'ElTableBody',
props: defaultProps,
setup(props) {
const instance = getCurrentInstance()
const parent = instance.parent as Table<DefaultRow>
const parent = inject(TABLE_INJECTION_KEY)
const { wrappedRowRender, tooltipContent, tooltipTrigger } =
useRender(props)
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent)

View File

@ -1,19 +1,13 @@
import { h, getCurrentInstance, computed } from 'vue'
import { h, computed, inject } from 'vue'
import { getRowIdentity } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import useEvents from './events-helper'
import useStyles from './styles-helper'
import type { TableBodyProps } from './defaults'
import type {
RenderRowData,
Table,
TreeNode,
TableProps,
} from '../table/defaults'
import type { RenderRowData, TreeNode, TableProps } from '../table/defaults'
function useRender<T>(props: Partial<TableBodyProps<T>>) {
const instance = getCurrentInstance()
const parent = instance.parent as Table<T>
const parent = inject(TABLE_INJECTION_KEY)
const {
handleDoubleClick,
handleClick,

View File

@ -1,16 +1,15 @@
import { getCurrentInstance } from 'vue'
import { inject } from 'vue'
import {
getFixedColumnOffset,
getFixedColumnsClass,
ensurePosition,
} from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
import type { TableBodyProps } from './defaults'
function useStyles<T>(props: Partial<TableBodyProps<T>>) {
const instance = getCurrentInstance()
const parent = instance.parent as Table<T>
const parent = inject(TABLE_INJECTION_KEY)
const getRowStyle = (row: T, rowIndex: number) => {
const rowStyle = parent.props.rowStyle

View File

@ -42,26 +42,18 @@ export default defineComponent({
},
},
setup(props) {
const { hasGutter, getCellClasses, getCellStyles, columns, gutterWidth } =
useStyle(props as TableFooter<DefaultRow>)
const { getCellClasses, getCellStyles, columns } = useStyle(
props as TableFooter<DefaultRow>
)
return {
getCellClasses,
getCellStyles,
hasGutter,
gutterWidth,
columns,
}
},
render() {
const {
hasGutter,
gutterWidth,
columns,
getCellStyles,
getCellClasses,
summaryMethod,
sumText,
} = this
const { columns, getCellStyles, getCellClasses, summaryMethod, sumText } =
this
const data = this.store.states.data.value
let sums = []
if (summaryMethod) {
@ -109,13 +101,8 @@ export default defineComponent({
border: '0',
},
[
hColgroup(columns, hasGutter),
h(
'tbody',
{
class: [{ 'has-gutter': hasGutter }],
},
[
hColgroup(columns),
h('tbody', [
h('tr', {}, [
...columns.map((column, cellIndex) =>
h(
@ -124,8 +111,8 @@ export default defineComponent({
key: cellIndex,
colspan: column.colSpan,
rowspan: column.rowSpan,
class: getCellClasses(columns, cellIndex, hasGutter),
style: getCellStyles(column, cellIndex, hasGutter),
class: getCellClasses(columns, cellIndex),
style: getCellStyles(column, cellIndex),
},
[
h(
@ -138,16 +125,8 @@ export default defineComponent({
]
)
),
hasGutter &&
h('td', {
class: 'el-table__fixed-right-patch el-table__cell',
style: {
width: `${gutterWidth}px`,
},
}),
]),
]
),
]),
]
)
},

View File

@ -1,38 +1,16 @@
import { computed, getCurrentInstance } from 'vue'
import {
getFixedColumnOffset,
getFixedColumnsClass,
ensurePosition,
ensureRightFixedStyle,
} from '../util'
import useMapState from './mapState-helper'
import type { Table } from '../table/defaults'
import type { TableColumnCtx } from '../table-column/defaults'
import type { TableFooter } from '.'
function useStyle<T>(props: TableFooter<T>) {
const instance = getCurrentInstance()
const table = instance.parent as Table<T>
const { columns } = useMapState<T>()
const hasGutter = computed(() => {
return (
!props.fixed &&
table.layout.gutterWidth > 0 &&
table.layout.height.value &&
table.layout.bodyScrollHeight.value > table.layout.bodyHeight.value
)
})
const gutterWidth = computed(() => {
return table.layout.gutterWidth
})
const getCellClasses = (
columns: TableColumnCtx<T>[],
cellIndex: number,
hasGutter: boolean
) => {
const getCellClasses = (columns: TableColumnCtx<T>[], cellIndex: number) => {
const column = columns[cellIndex]
const classes = [
'el-table__cell',
@ -47,31 +25,21 @@ function useStyle<T>(props: TableFooter<T>) {
if (!column.children) {
classes.push('is-leaf')
}
if (hasGutter && cellIndex === columns.length - 1) {
classes.push('last')
}
return classes
}
const getCellStyles = (
column: TableColumnCtx<T>,
cellIndex: number,
hasGutter: boolean
) => {
const getCellStyles = (column: TableColumnCtx<T>, cellIndex: number) => {
const fixedStyle = getFixedColumnOffset(
cellIndex,
column.fixed,
props.store
)
ensureRightFixedStyle(fixedStyle, hasGutter)
ensurePosition(fixedStyle, 'left')
ensurePosition(fixedStyle, 'right')
return fixedStyle
}
return {
hasGutter,
gutterWidth,
getCellClasses,
getCellStyles,
columns,

View File

@ -1,7 +1,6 @@
import {
defineComponent,
getCurrentInstance,
computed,
onMounted,
nextTick,
ref,
@ -62,18 +61,7 @@ export default defineComponent({
const parent = instance.parent as Table<unknown>
const storeData = parent.store.states
const filterPanels = ref({})
const { tableLayout, onColumnsChange, onScrollableChange } =
useLayoutObserver(parent)
const hasGutter = computed(() => {
return (
!props.fixed &&
tableLayout.gutterWidth > 0 &&
tableLayout.bodyScrollHeight.value > tableLayout.bodyHeight.value
)
})
const gutterWidth = computed(() => {
return tableLayout.gutterWidth
})
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent)
onMounted(() => {
nextTick(() => {
const { prop, order } = props.defaultSort
@ -110,8 +98,6 @@ export default defineComponent({
return {
columns: storeData.columns,
filterPanels,
hasGutter,
gutterWidth,
onColumnsChange,
onScrollableChange,
columnRows,
@ -134,9 +120,7 @@ export default defineComponent({
const {
columns,
isGroup,
hasGutter,
columnRows,
gutterWidth,
getHeaderCellStyle,
getHeaderCellClass,
getHeaderRowClass,
@ -160,11 +144,11 @@ export default defineComponent({
class: 'el-table__header',
},
[
hColgroup(columns, hasGutter),
hColgroup(columns),
h(
'thead',
{
class: { 'is-group': isGroup, 'has-gutter': hasGutter },
class: { 'is-group': isGroup },
},
columnRows.map((subColumns, rowIndex) =>
h(
@ -174,8 +158,7 @@ export default defineComponent({
key: rowIndex,
style: getHeaderRowStyle(rowIndex),
},
subColumns
.map((column, cellIndex) => {
subColumns.map((column, cellIndex) => {
if (column.rowSpan > rowSpan) {
rowSpan = column.rowSpan
}
@ -195,8 +178,7 @@ export default defineComponent({
rowIndex,
cellIndex,
subColumns,
column,
hasGutter
column
),
onClick: ($event) => handleHeaderClick($event, column),
onContextmenu: ($event) =>
@ -238,20 +220,12 @@ export default defineComponent({
[
h('i', {
onClick: ($event) =>
handleSortClick(
$event,
column,
'ascending'
),
handleSortClick($event, column, 'ascending'),
class: 'sort-caret ascending',
}),
h('i', {
onClick: ($event) =>
handleSortClick(
$event,
column,
'descending'
),
handleSortClick($event, column, 'descending'),
class: 'sort-caret descending',
}),
]
@ -259,8 +233,7 @@ export default defineComponent({
column.filterable &&
h(FilterPanel, {
store: $parent.store,
placement:
column.filterPlacement || 'bottom-start',
placement: column.filterPlacement || 'bottom-start',
column,
upDataColumn: (key, value) => {
column[key] = value
@ -271,24 +244,6 @@ export default defineComponent({
]
)
})
.concat(
hasGutter && rowIndex === 0
? [
h(
'th',
{
class: 'el-table__fixed-right-patch el-table__cell',
key: `el-table--scrollbar`,
rowSpan,
style: {
width: `${gutterWidth}px`,
},
},
[]
),
]
: []
)
)
)
),

View File

@ -3,7 +3,6 @@ import {
getFixedColumnsClass,
getFixedColumnOffset,
ensurePosition,
ensureRightFixedStyle,
} from '../util'
import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
@ -37,8 +36,7 @@ function useStyle<T>(props: TableHeaderProps<T>) {
rowIndex: number,
columnIndex: number,
row: T,
column: TableColumnCtx<T>,
hasGutter: boolean
column: TableColumnCtx<T>
) => {
let headerCellStyles = parent.props.headerCellStyle ?? {}
if (typeof headerCellStyles === 'function') {
@ -55,7 +53,6 @@ function useStyle<T>(props: TableHeaderProps<T>) {
props.store,
row as unknown as TableColumnCtx<T>[]
)
ensureRightFixedStyle(fixedStyle, hasGutter)
ensurePosition(fixedStyle, 'left')
ensurePosition(fixedStyle, 'right')
return Object.assign({}, headerCellStyles, fixedStyle)

View File

@ -1,7 +1,6 @@
import { nextTick, ref, isRef } from 'vue'
import { hasOwn } from '@vue/shared'
import { isClient } from '@vueuse/core'
import scrollbarWidth from '@element-plus/utils/scrollbar-width'
import { parseHeight } from './util'
import type { Ref } from 'vue'
@ -53,7 +52,7 @@ class TableLayout<T> {
this.bodyHeight = ref(null)
this.bodyScrollHeight = ref(0)
this.fixedBodyHeight = ref(null)
this.gutterWidth = scrollbarWidth()
this.gutterWidth = 0
for (const name in options) {
if (hasOwn(options, name)) {
if (isRef(this[name])) {

View File

@ -15,6 +15,7 @@
'el-table--enable-row-transition':
(store.states.data.value || []).length !== 0 &&
(store.states.data.value || []).length < 100,
'has-footer': showSummary,
},
tableSize ? `el-table--${tableSize}` : '',
className,
@ -43,6 +44,7 @@
/>
</div>
<div ref="bodyWrapper" :style="bodyHeight" class="el-table__body-wrapper">
<el-scrollbar ref="scrollWrapper" :height="height">
<table-body
:context="context"
:highlight="highlightCurrentRow"
@ -72,13 +74,9 @@
>
<slot name="append"></slot>
</div>
</el-scrollbar>
</div>
<div v-if="border || isGroup" class="el-table__border-left-patch"></div>
<div
v-if="layout.scrollX.value && layout.height.value"
class="el-table__border-bottom-patch"
:style="borderBottomPatchStyles"
></div>
</div>
<div
v-if="showSummary"
@ -105,10 +103,11 @@
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, computed } from 'vue'
import { defineComponent, getCurrentInstance, computed, provide } from 'vue'
import debounce from 'lodash/debounce'
import { Mousewheel } from '@element-plus/directives'
import { useLocale } from '@element-plus/hooks'
import ElScrollbar from '@element-plus/components/scrollbar'
import { createStore } from './store/helper'
import TableLayout from './table-layout'
import TableHeader from './table-header'
@ -117,6 +116,7 @@ import TableFooter from './table-footer'
import useUtils from './table/utils-helper'
import useStyle from './table/style-helper'
import defaultProps from './table/defaults'
import { TABLE_INJECTION_KEY } from './tokens'
import type { Table } from './table/defaults'
@ -130,6 +130,7 @@ export default defineComponent({
TableHeader,
TableBody,
TableFooter,
ElScrollbar,
},
props: defaultProps,
emits: [
@ -156,6 +157,7 @@ export default defineComponent({
type Row = typeof props.data[number]
const { t } = useLocale()
const table = getCurrentInstance() as Table<Row>
provide(TABLE_INJECTION_KEY, table)
const store = createStore<Row>(table, props)
table.store = store
const layout = new TableLayout<Row>({
@ -190,6 +192,7 @@ export default defineComponent({
handleHeaderFooterMousewheel,
tableSize,
bodyHeight,
height,
emptyBlockStyle,
handleFixedMousewheel,
fixedHeight,
@ -199,7 +202,6 @@ export default defineComponent({
resizeState,
doLayout,
tableBodyStyles,
borderBottomPatchStyles,
} = useStyle<Row>(props, layout, store, table)
const debouncedUpdateLayout = debounce(doLayout, 50)
@ -235,9 +237,9 @@ export default defineComponent({
isGroup,
bodyWidth,
bodyHeight,
height,
tableBodyStyles,
emptyBlockStyle,
borderBottomPatchStyles,
debouncedUpdateLayout,
handleFixedMousewheel,
fixedHeight,

View File

@ -1,6 +1,6 @@
import {
onMounted,
onUnmounted,
onBeforeUnmount,
computed,
ref,
watchEffect,
@ -99,13 +99,6 @@ function useStyle<T>(
}
})
const borderBottomPatchStyles = computed(() => {
return {
bottom: `${layout.gutterWidth}px`,
right: `${layout.gutterWidth}px`,
}
})
const doLayout = () => {
if (shouldUpdateHeight.value) {
layout.updateElsHeight()
@ -150,8 +143,10 @@ function useStyle<T>(
setScrollClassByEl(tableWrapper, className)
}
const syncPostion = function () {
if (!table.refs.bodyWrapper) return
const { scrollLeft, offsetWidth, scrollWidth } = table.refs.bodyWrapper
if (!table.refs.scrollWrapper) return
const scrollContainer = table.refs.scrollWrapper.wrap$
if (!scrollContainer) return
const { scrollLeft, offsetWidth, scrollWidth } = scrollContainer
const { headerWrapper, footerWrapper } = table.refs
if (headerWrapper) headerWrapper.scrollLeft = scrollLeft
if (footerWrapper) footerWrapper.scrollLeft = scrollLeft
@ -166,7 +161,7 @@ function useStyle<T>(
}
const bindEvents = () => {
table.refs.bodyWrapper.addEventListener('scroll', syncPostion, {
table.refs.scrollWrapper.wrap$?.addEventListener('scroll', syncPostion, {
passive: true,
})
if (props.fit) {
@ -175,11 +170,15 @@ function useStyle<T>(
on(window, 'resize', doLayout)
}
}
onUnmounted(() => {
onBeforeUnmount(() => {
unbindEvents()
})
const unbindEvents = () => {
table.refs.bodyWrapper?.removeEventListener('scroll', syncPostion, true)
table.refs.scrollWrapper.wrap$?.removeEventListener(
'scroll',
syncPostion,
true
)
if (props.fit) {
removeResizeListener(table.vnode.el as ResizableElement, resizeListener)
} else {
@ -217,6 +216,20 @@ function useStyle<T>(
? `${(bodyWidth_.value as number) - (scrollY.value ? gutterWidth : 0)}px`
: ''
})
const height = computed(() => {
const headerHeight = layout.headerHeight.value || 0
const bodyHeight = layout.bodyHeight.value
const footerHeight = layout.footerHeight.value || 0
if (props.height) {
return bodyHeight ? bodyHeight : undefined
} else if (props.maxHeight) {
const maxHeight = parseHeight(props.maxHeight)
return maxHeight - footerHeight - (props.showHeader ? headerHeight : 0)
}
return undefined
})
const bodyHeight = computed(() => {
const headerHeight = layout.headerHeight.value || 0
const bodyHeight = layout.bodyHeight.value
@ -331,6 +344,7 @@ function useStyle<T>(
handleHeaderFooterMousewheel,
tableSize,
bodyHeight,
height,
emptyBlockStyle,
handleFixedMousewheel,
fixedHeight,
@ -340,7 +354,6 @@ function useStyle<T>(
resizeState,
doLayout,
tableBodyStyles,
borderBottomPatchStyles,
}
}

View File

@ -0,0 +1,5 @@
import type { InjectionKey } from 'vue'
import type { DefaultRow, Table } from './table/defaults'
export const TABLE_INJECTION_KEY: InjectionKey<Table<DefaultRow>> =
Symbol('ElTable')

View File

@ -2,7 +2,6 @@ import { hasOwn } from '@vue/shared'
import { createPopper } from '@popperjs/core'
import { PopupManager } from '@element-plus/utils/popup-manager'
import { getValueByPath } from '@element-plus/utils/util'
import scrollbarWidth from '@element-plus/utils/scrollbar-width'
import { off, on } from '@element-plus/utils/dom'
import type {
@ -489,26 +488,6 @@ export const getFixedColumnOffset = <T>(
return styles
}
export function getCellStyle<T>(
column: TableColumnCtx<T>,
cellIndex: number,
hasGutter: boolean,
gutterWidth: number,
store: any
) {
const fixedStyle = getFixedColumnOffset(cellIndex, column.fixed, store)
ensureRightFixedStyle(fixedStyle, hasGutter)
ensurePosition(fixedStyle, 'left')
ensurePosition(fixedStyle, 'right')
return fixedStyle
}
export const ensureRightFixedStyle = (style, hasGutter: boolean) => {
if (hasGutter && style && !Number.isNaN(style.right)) {
style.right += scrollbarWidth()
}
}
export const ensurePosition = (style, key: string) => {
if (!style) return
if (!Number.isNaN(style[key])) {

View File

@ -83,10 +83,12 @@
padding: 10px;
label.#{$namespace}-checkbox {
display: block;
display: flex;
align-items: center;
margin-right: 5px;
margin-bottom: 8px;
margin-bottom: 12px;
margin-left: 5px;
height: unset;
}
.#{$namespace}-checkbox:last-child {

View File

@ -32,6 +32,14 @@
}
}
&.has-footer {
@include e(inner-wrapper) {
&::before {
bottom: 1px;
}
}
}
// when data is empty
@include e(empty-block) {
min-height: 60px;
@ -263,7 +271,7 @@
content: '';
position: absolute;
background-color: var(--el-table-border-color);
z-index: 1;
z-index: 3;
}
}
@ -309,11 +317,6 @@
@include e(footer-wrapper) {
margin-top: -2px;
td.#{$namespace}-table__cell {
&.last {
border-right: none;
}
}
}
// fix: #1013
@ -403,6 +406,14 @@
}
}
@include e((header-wrapper, body-wrapper)) {
.#{$namespace}-table-column--selection {
.el-checkbox {
height: unset;
}
}
}
@include when(scrolling-left) {
.#{$namespace}-table-fixed-column--left.is-last-column {
&::before {
@ -452,6 +463,9 @@
@include e(body-wrapper) {
overflow: hidden;
position: relative;
.#{$namespace}-scrollbar__bar {
z-index: 2;
}
}
.caret-wrapper {
@ -546,9 +560,8 @@
& i {
color: var(--el-color-info);
font-size: 12px;
font-size: 14px;
vertical-align: middle;
transform: scale(0.75);
}
}