refactor(components): [el-table] use namespace (#5528)

This commit is contained in:
msidolphin 2022-01-22 10:37:59 +08:00 committed by GitHub
parent dd5b84f885
commit 2f521c419c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 214 additions and 148 deletions

View File

@ -10,15 +10,13 @@
</el-table-column> </el-table-column>
<el-table-column label="Name" width="180"> <el-table-column label="Name" width="180">
<template #default="scope"> <template #default="scope">
<el-popover effect="light" trigger="hover" placement="top"> <el-popover effect="light" trigger="hover" placement="top" width="auto">
<template #default> <template #default>
<p>name: {{ scope.row.name }}</p> <div>name: {{ scope.row.name }}</div>
<p>address: {{ scope.row.address }}</p> <div>address: {{ scope.row.address }}</div>
</template> </template>
<template #reference> <template #reference>
<div class="name-wrapper"> <el-tag>{{ scope.row.name }}</el-tag>
<el-tag>{{ scope.row.name }}</el-tag>
</div>
</template> </template>
</el-popover> </el-popover>
</template> </template>

View File

@ -9,6 +9,11 @@ import type { TableColumnCtx } from './table-column/defaults'
import type { Store } from './store' import type { Store } from './store'
import type { TreeNode } from './table/defaults' import type { TreeNode } from './table/defaults'
const defaultClassNames = {
selection: 'table-column--selection',
expand: 'table__expand-column',
}
export const cellStarts = { export const cellStarts = {
default: { default: {
order: '', order: '',
@ -18,7 +23,6 @@ export const cellStarts = {
minWidth: 48, minWidth: 48,
realWidth: 48, realWidth: 48,
order: '', order: '',
className: 'el-table-column--selection',
}, },
expand: { expand: {
width: 48, width: 48,
@ -34,6 +38,10 @@ export const cellStarts = {
}, },
} }
export const getDefaultClassName = (type) => {
return defaultClassNames[type] || ''
}
// 这些选项不应该被覆盖 // 这些选项不应该被覆盖
export const cellForced = { export const cellForced = {
selection: { selection: {
@ -105,9 +113,10 @@ export const cellForced = {
return column.label || '' return column.label || ''
}, },
renderCell<T>({ row, store }: { row: T; store: Store<T> }) { renderCell<T>({ row, store }: { row: T; store: Store<T> }) {
const classes = ['el-table__expand-icon'] const { ns } = store
const classes = [ns.e('expand-icon')]
if (store.states.expandRows.value.indexOf(row) > -1) { if (store.states.expandRows.value.indexOf(row) > -1) {
classes.push('el-table__expand-icon--expanded') classes.push(ns.em('expand-icon', 'expanded'))
} }
const callback = function (e: Event) { const callback = function (e: Event) {
e.stopPropagation() e.stopPropagation()
@ -134,7 +143,6 @@ export const cellForced = {
}, },
sortable: false, sortable: false,
resizable: false, resizable: false,
className: 'el-table__expand-column',
}, },
} }
@ -170,18 +178,19 @@ export function treeCellPrefix<T>({
e.stopPropagation() e.stopPropagation()
store.loadOrToggle(row) store.loadOrToggle(row)
} }
const { ns } = store
if (treeNode.indent) { if (treeNode.indent) {
ele.push( ele.push(
h('span', { h('span', {
class: 'el-table__indent', class: ns.e('indent'),
style: { 'padding-left': `${treeNode.indent}px` }, style: { 'padding-left': `${treeNode.indent}px` },
}) })
) )
} }
if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) { if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) {
const expandClasses = [ const expandClasses = [
'el-table__expand-icon', ns.e('expand-icon'),
treeNode.expanded ? 'el-table__expand-icon--expanded' : '', treeNode.expanded ? ns.em('expand-icon', 'expanded') : '',
] ]
let icon = ArrowRight let icon = ArrowRight
if (treeNode.loading) { if (treeNode.loading) {
@ -200,7 +209,7 @@ export function treeCellPrefix<T>({
return [ return [
h( h(
ElIcon, ElIcon,
{ class: { 'is-loading': treeNode.loading } }, { class: { [ns.is('loading')]: treeNode.loading } },
{ {
default: () => [h(icon)], default: () => [h(icon)],
} }
@ -213,7 +222,7 @@ export function treeCellPrefix<T>({
} else { } else {
ele.push( ele.push(
h('span', { h('span', {
class: 'el-table__placeholder', class: ns.e('placeholder'),
}) })
) )
} }

View File

@ -9,16 +9,16 @@
append-to-body append-to-body
effect="light" effect="light"
pure pure
popper-class="el-table-filter" :popper-class="ns.b()"
persistent persistent
> >
<template #content> <template #content>
<div v-if="multiple"> <div v-if="multiple">
<div class="el-table-filter__content"> <div :class="ns.e('content')">
<el-scrollbar wrap-class="el-table-filter__wrap"> <el-scrollbar :wrap-class="ns.e('wrap')">
<el-checkbox-group <el-checkbox-group
v-model="filteredValue" v-model="filteredValue"
class="el-table-filter__checkbox-group" :class="ns.e('checkbox-group')"
> >
<el-checkbox <el-checkbox
v-for="filter in filters" v-for="filter in filters"
@ -30,9 +30,9 @@
</el-checkbox-group> </el-checkbox-group>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div class="el-table-filter__bottom"> <div :class="ns.e('bottom')">
<button <button
:class="{ 'is-disabled': filteredValue.length === 0 }" :class="{ [ns.is('disabled')]: filteredValue.length === 0 }"
:disabled="filteredValue.length === 0" :disabled="filteredValue.length === 0"
type="button" type="button"
@click="handleConfirm" @click="handleConfirm"
@ -44,12 +44,15 @@
</button> </button>
</div> </div>
</div> </div>
<ul v-else class="el-table-filter__list"> <ul v-else :class="ns.e('list')">
<li <li
:class="{ :class="[
'is-active': filterValue === undefined || filterValue === null, ns.e('list-item'),
}" {
class="el-table-filter__list-item" [ns.is('active')]:
filterValue === undefined || filterValue === null,
},
]"
@click="handleSelect(null)" @click="handleSelect(null)"
> >
{{ t('el.table.clearFilter') }} {{ t('el.table.clearFilter') }}
@ -57,9 +60,8 @@
<li <li
v-for="filter in filters" v-for="filter in filters"
:key="filter.value" :key="filter.value"
:class="{ 'is-active': isActive(filter) }" :class="[ns.e('list-item'), ns.is('active', isActive(filter))]"
:label="filter.value" :label="filter.value"
class="el-table-filter__list-item"
@click="handleSelect(filter.value)" @click="handleSelect(filter.value)"
> >
{{ filter.text }} {{ filter.text }}
@ -69,7 +71,10 @@
<template #default> <template #default>
<span <span
v-click-outside:[popperPaneRef]="hideFilterPanel" v-click-outside:[popperPaneRef]="hideFilterPanel"
class="el-table__column-filter-trigger el-none-outline" :class="[
`${ns.namespace.value}-table__column-filter-trigger`,
`${ns.namespace.value}-none-outline`,
]"
@click="showFilterPanel" @click="showFilterPanel"
> >
<el-icon> <el-icon>
@ -87,7 +92,7 @@ import ElCheckbox from '@element-plus/components/checkbox'
import { ElIcon } from '@element-plus/components/icon' import { ElIcon } from '@element-plus/components/icon'
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue' import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
import { ClickOutside } from '@element-plus/directives' import { ClickOutside } from '@element-plus/directives'
import { useLocale } from '@element-plus/hooks' import { useLocale, useNamespace } from '@element-plus/hooks'
import ElTooltip from '@element-plus/components/tooltip' import ElTooltip from '@element-plus/components/tooltip'
import ElScrollbar from '@element-plus/components/scrollbar' import ElScrollbar from '@element-plus/components/scrollbar'
import type { Placement } from '@element-plus/components/popper' import type { Placement } from '@element-plus/components/popper'
@ -129,7 +134,8 @@ export default defineComponent({
setup(props) { setup(props) {
const instance = getCurrentInstance() const instance = getCurrentInstance()
const { t } = useLocale() const { t } = useLocale()
const parent = instance.parent as TableHeader const ns = useNamespace('table-filter')
const parent = instance?.parent as TableHeader
if (!parent.filterPanels.value[props.column.id]) { if (!parent.filterPanels.value[props.column.id]) {
parent.filterPanels.value[props.column.id] = instance parent.filterPanels.value[props.column.id] = instance
} }
@ -139,7 +145,7 @@ export default defineComponent({
return props.column && props.column.filters return props.column && props.column.filters
}) })
const filterValue = computed({ const filterValue = computed({
get: () => (props.column.filteredValue || [])[0], get: () => (props.column?.filteredValue || [])[0],
set: (value: string) => { set: (value: string) => {
if (filteredValue.value) { if (filteredValue.value) {
if (typeof value !== 'undefined' && value !== null) { if (typeof value !== 'undefined' && value !== null) {
@ -235,6 +241,7 @@ export default defineComponent({
handleSelect, handleSelect,
isActive, isActive,
t, t,
ns,
showFilterPanel, showFilterPanel,
hideFilterPanel, hideFilterPanel,
popperPaneRef, popperPaneRef,

View File

@ -1,4 +1,5 @@
import { nextTick, getCurrentInstance, unref } from 'vue' import { nextTick, getCurrentInstance, unref } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import useWatcher from './watcher' import useWatcher from './watcher'
import type { Ref } from 'vue' import type { Ref } from 'vue'
@ -37,6 +38,7 @@ function sortColumn<T>(array: TableColumnCtx<T>[]) {
function useStore<T>() { function useStore<T>() {
const instance = getCurrentInstance() as Table<T> const instance = getCurrentInstance() as Table<T>
const watcher = useWatcher<T>() const watcher = useWatcher<T>()
const ns = useNamespace('table')
type StoreStates = typeof watcher.states type StoreStates = typeof watcher.states
const mutations = { const mutations = {
setData(states: StoreStates, data: T[]) { setData(states: StoreStates, data: T[]) {
@ -200,6 +202,7 @@ function useStore<T>() {
nextTick(() => instance.layout.updateScrollY.apply(instance.layout)) nextTick(() => instance.layout.updateScrollY.apply(instance.layout))
} }
return { return {
ns,
...watcher, ...watcher,
mutations, mutations,
commit, commit,

View File

@ -14,18 +14,20 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
const table = parent const table = parent
const cell = getCell(event) const cell = getCell(event)
let column: TableColumnCtx<T> let column: TableColumnCtx<T>
const namespace = table?.vnode.el?.dataset.prefix
if (cell) { if (cell) {
column = getColumnByCell( column = getColumnByCell(
{ {
columns: props.store.states.columns.value, columns: props.store.states.columns.value,
}, },
cell cell,
namespace
) )
if (column) { if (column) {
table.emit(`cell-${name}`, row, column, cell, event) table?.emit(`cell-${name}`, row, column, cell, event)
} }
} }
table.emit(`row-${name}`, row, column, event) table?.emit(`row-${name}`, row, column, event)
} }
const handleDoubleClick = (event: Event, row: T) => { const handleDoubleClick = (event: Event, row: T) => {
handleEvent(event, row, 'dblclick') handleEvent(event, row, 'dblclick')
@ -49,16 +51,17 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
) => { ) => {
const table = parent const table = parent
const cell = getCell(event) const cell = getCell(event)
const namespace = table?.vnode.el?.dataset.prefix
if (cell) { if (cell) {
const column = getColumnByCell( const column = getColumnByCell(
{ {
columns: props.store.states.columns.value, columns: props.store.states.columns.value,
}, },
cell cell,
namespace
) )
const hoverState = (table.hoverState = { cell, column, row }) const hoverState = (table.hoverState = { cell, column, row })
table.emit( table?.emit(
'cell-mouse-enter', 'cell-mouse-enter',
hoverState.row, hoverState.row,
hoverState.column, hoverState.column,
@ -71,7 +74,12 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
const cellChild = (event.target as HTMLElement).querySelector( const cellChild = (event.target as HTMLElement).querySelector(
'.cell' '.cell'
) as HTMLElement ) as HTMLElement
if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) { if (
!(
hasClass(cellChild, `${namespace}-tooltip`) &&
cellChild.childNodes.length
)
) {
return return
} }
// use range width instead of scrollWidth to determine whether the text is overflowing // use range width instead of scrollWidth to determine whether the text is overflowing
@ -102,8 +110,8 @@ function useEvents<T>(props: Partial<TableBodyProps<T>>) {
const cell = getCell(event) const cell = getCell(event)
if (!cell) return if (!cell) return
const oldHoverState = parent.hoverState const oldHoverState = parent?.hoverState
parent.emit( parent?.emit(
'cell-mouse-leave', 'cell-mouse-leave',
oldHoverState?.row, oldHoverState?.row,
oldHoverState?.column, oldHoverState?.column,

View File

@ -9,6 +9,7 @@ import {
} from 'vue' } from 'vue'
import { isClient } from '@vueuse/core' import { isClient } from '@vueuse/core'
import { addClass, removeClass } from '@element-plus/utils/dom' import { addClass, removeClass } from '@element-plus/utils/dom'
import { useNamespace } from '@element-plus/hooks'
import { hColgroup } from '../h-helper' import { hColgroup } from '../h-helper'
import useLayoutObserver from '../layout-observer' import useLayoutObserver from '../layout-observer'
import { removePopper } from '../util' import { removePopper } from '../util'
@ -24,9 +25,10 @@ export default defineComponent({
setup(props) { setup(props) {
const instance = getCurrentInstance() const instance = getCurrentInstance()
const parent = inject(TABLE_INJECTION_KEY) const parent = inject(TABLE_INJECTION_KEY)
const ns = useNamespace('table')
const { wrappedRowRender, tooltipContent, tooltipTrigger } = const { wrappedRowRender, tooltipContent, tooltipTrigger } =
useRender(props) useRender(props)
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent) const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent!)
watch(props.store.states.hoverRow, (newVal: any, oldVal: any) => { watch(props.store.states.hoverRow, (newVal: any, oldVal: any) => {
if (!props.store.states.isComplex.value || !isClient) return if (!props.store.states.isComplex.value || !isClient) return
@ -35,7 +37,7 @@ export default defineComponent({
raf = (fn) => window.setTimeout(fn, 16) raf = (fn) => window.setTimeout(fn, 16)
} }
raf(() => { raf(() => {
const rows = instance.vnode.el.querySelectorAll('.el-table__row') const rows = instance?.vnode.el?.querySelectorAll(ns.e('row'))
const oldRow = rows[oldVal] const oldRow = rows[oldVal]
const newRow = rows[newVal] const newRow = rows[newVal]
if (oldRow) { if (oldRow) {
@ -55,6 +57,7 @@ export default defineComponent({
}) })
return { return {
ns,
onColumnsChange, onColumnsChange,
onScrollableChange, onScrollableChange,
wrappedRowRender, wrappedRowRender,
@ -63,13 +66,13 @@ export default defineComponent({
} }
}, },
render() { render() {
const { wrappedRowRender, store } = this const { ns, wrappedRowRender, store } = this
const data = store.states.data.value || [] const data = store.states.data.value || []
const columns = store.states.columns.value const columns = store.states.columns.value
return h( return h(
'table', 'table',
{ {
class: 'el-table__body', class: ns.e('body'),
cellspacing: '0', cellspacing: '0',
cellpadding: '0', cellpadding: '0',
border: '0', border: '0',

View File

@ -1,4 +1,5 @@
import { inject } from 'vue' import { inject } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { import {
getFixedColumnOffset, getFixedColumnOffset,
getFixedColumnsClass, getFixedColumnsClass,
@ -10,9 +11,10 @@ import type { TableBodyProps } from './defaults'
function useStyles<T>(props: Partial<TableBodyProps<T>>) { function useStyles<T>(props: Partial<TableBodyProps<T>>) {
const parent = inject(TABLE_INJECTION_KEY) const parent = inject(TABLE_INJECTION_KEY)
const ns = useNamespace('table')
const getRowStyle = (row: T, rowIndex: number) => { const getRowStyle = (row: T, rowIndex: number) => {
const rowStyle = parent.props.rowStyle const rowStyle = parent?.props.rowStyle
if (typeof rowStyle === 'function') { if (typeof rowStyle === 'function') {
return rowStyle.call(null, { return rowStyle.call(null, {
row, row,
@ -23,18 +25,18 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
} }
const getRowClass = (row: T, rowIndex: number) => { const getRowClass = (row: T, rowIndex: number) => {
const classes = ['el-table__row'] const classes = [ns.e('row')]
if ( if (
parent.props.highlightCurrentRow && parent?.props.highlightCurrentRow &&
row === props.store.states.currentRow.value row === props.store.states.currentRow.value
) { ) {
classes.push('current-row') classes.push('current-row')
} }
if (props.stripe && rowIndex % 2 === 1) { if (props.stripe && rowIndex % 2 === 1) {
classes.push('el-table__row--striped') classes.push(ns.em('row', 'striped'))
} }
const rowClassName = parent.props.rowClassName const rowClassName = parent?.props.rowClassName
if (typeof rowClassName === 'string') { if (typeof rowClassName === 'string') {
classes.push(rowClassName) classes.push(rowClassName)
} else if (typeof rowClassName === 'function') { } else if (typeof rowClassName === 'function') {
@ -59,7 +61,7 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
row: T, row: T,
column: TableColumnCtx<T> column: TableColumnCtx<T>
) => { ) => {
const cellStyle = parent.props.cellStyle const cellStyle = parent?.props.cellStyle
let cellStyles = cellStyle ?? {} let cellStyles = cellStyle ?? {}
if (typeof cellStyle === 'function') { if (typeof cellStyle === 'function') {
cellStyles = cellStyle.call(null, { cellStyles = cellStyle.call(null, {
@ -71,7 +73,7 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
} }
const fixedStyle = getFixedColumnOffset( const fixedStyle = getFixedColumnOffset(
columnIndex, columnIndex,
props.fixed, props?.fixed,
props.store props.store
) )
ensurePosition(fixedStyle, 'left') ensurePosition(fixedStyle, 'left')
@ -87,9 +89,9 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
) => { ) => {
const fixedClasses = column.isSubColumn const fixedClasses = column.isSubColumn
? [] ? []
: getFixedColumnsClass(columnIndex, props.fixed, props.store) : getFixedColumnsClass(ns.b(), columnIndex, props?.fixed, props.store)
const classes = [column.id, column.align, column.className, ...fixedClasses] const classes = [column.id, column.align, column.className, ...fixedClasses]
const cellClassName = parent.props.cellClassName const cellClassName = parent?.props.cellClassName
if (typeof cellClassName === 'string') { if (typeof cellClassName === 'string') {
classes.push(cellClassName) classes.push(cellClassName)
} else if (typeof cellClassName === 'function') { } else if (typeof cellClassName === 'function') {
@ -102,9 +104,7 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
}) })
) )
} }
classes.push(ns.e('cell'))
classes.push('el-table__cell')
return classes.join(' ') return classes.join(' ')
} }
const getSpan = ( const getSpan = (
@ -115,7 +115,7 @@ function useStyles<T>(props: Partial<TableBodyProps<T>>) {
) => { ) => {
let rowspan = 1 let rowspan = 1
let colspan = 1 let colspan = 1
const fn = parent.props.spanMethod const fn = parent?.props.spanMethod
if (typeof fn === 'function') { if (typeof fn === 'function') {
const result = fn({ const result = fn({
row, row,

View File

@ -1,6 +1,12 @@
import { getCurrentInstance, h, ref, computed, watchEffect } from 'vue' import { getCurrentInstance, h, ref, computed, watchEffect, unref } from 'vue'
import { debugWarn } from '@element-plus/utils/error' import { debugWarn } from '@element-plus/utils/error'
import { cellForced, defaultRenderCell, treeCellPrefix } from '../config' import { useNamespace } from '@element-plus/hooks'
import {
cellForced,
defaultRenderCell,
treeCellPrefix,
getDefaultClassName,
} from '../config'
import { parseWidth, parseMinWidth } from '../util' import { parseWidth, parseMinWidth } from '../util'
import type { ComputedRef } from 'vue' import type { ComputedRef } from 'vue'
@ -16,6 +22,7 @@ function useRender<T>(
const isSubColumn = ref(false) const isSubColumn = ref(false)
const realAlign = ref<string>() const realAlign = ref<string>()
const realHeaderAlign = ref<string>() const realHeaderAlign = ref<string>()
const ns = useNamespace('table')
watchEffect(() => { watchEffect(() => {
realAlign.value = props.align ? `is-${props.align}` : null realAlign.value = props.align ? `is-${props.align}` : null
// nextline help render // nextline help render
@ -57,10 +64,17 @@ function useRender<T>(
const source = cellForced[type] || {} const source = cellForced[type] || {}
Object.keys(source).forEach((prop) => { Object.keys(source).forEach((prop) => {
const value = source[prop] const value = source[prop]
if (value !== undefined) { if (prop !== 'className' && value !== undefined) {
column[prop] = prop === 'className' ? `${column[prop]} ${value}` : value column[prop] = value
} }
}) })
const className = getDefaultClassName(type)
if (className) {
const forceClass = `${unref(ns.namespace)}-${className}`
column.className = column.className
? `${column.className} ${forceClass}`
: forceClass
}
return column return column
} }
@ -123,7 +137,7 @@ function useRender<T>(
style: {}, style: {},
} }
if (column.showOverflowTooltip) { if (column.showOverflowTooltip) {
props.class += ' el-tooltip' props.class = `${props.class} ${unref(ns.namespace)}-tooltip`
props.style = { props.style = {
width: `${ width: `${
(data.column.realWidth || Number(data.column.width)) - 1 (data.column.realWidth || Number(data.column.width)) - 1

View File

@ -1,4 +1,5 @@
import { defineComponent, h } from 'vue' import { defineComponent, h } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { hColgroup } from '../h-helper' import { hColgroup } from '../h-helper'
import useStyle from './style-helper' import useStyle from './style-helper'
import type { Store } from '../store' import type { Store } from '../store'
@ -45,15 +46,23 @@ export default defineComponent({
const { getCellClasses, getCellStyles, columns } = useStyle( const { getCellClasses, getCellStyles, columns } = useStyle(
props as TableFooter<DefaultRow> props as TableFooter<DefaultRow>
) )
const ns = useNamespace('table')
return { return {
ns,
getCellClasses, getCellClasses,
getCellStyles, getCellStyles,
columns, columns,
} }
}, },
render() { render() {
const { columns, getCellStyles, getCellClasses, summaryMethod, sumText } = const {
this columns,
getCellStyles,
getCellClasses,
summaryMethod,
sumText,
ns,
} = this
const data = this.store.states.data.value const data = this.store.states.data.value
let sums = [] let sums = []
if (summaryMethod) { if (summaryMethod) {
@ -95,7 +104,7 @@ export default defineComponent({
return h( return h(
'table', 'table',
{ {
class: 'el-table__footer', class: ns.e('footer'),
cellspacing: '0', cellspacing: '0',
cellpadding: '0', cellpadding: '0',
border: '0', border: '0',

View File

@ -1,11 +1,9 @@
import { computed, getCurrentInstance } from 'vue' import { computed, inject } from 'vue'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { Table } from '../table/defaults' function useMapState() {
const table = inject(TABLE_INJECTION_KEY)
function useMapState<T>() { const store = table?.store
const instance = getCurrentInstance()
const table = instance.parent as Table<T>
const store = table.store
const leftFixedLeafCount = computed(() => { const leftFixedLeafCount = computed(() => {
return store.states.fixedLeafColumnsLength.value return store.states.fixedLeafColumnsLength.value
}) })

View File

@ -1,3 +1,4 @@
import { useNamespace } from '@element-plus/hooks'
import { import {
getFixedColumnOffset, getFixedColumnOffset,
getFixedColumnsClass, getFixedColumnsClass,
@ -8,22 +9,23 @@ import type { TableColumnCtx } from '../table-column/defaults'
import type { TableFooter } from '.' import type { TableFooter } from '.'
function useStyle<T>(props: TableFooter<T>) { function useStyle<T>(props: TableFooter<T>) {
const { columns } = useMapState<T>() const { columns } = useMapState()
const ns = useNamespace('table')
const getCellClasses = (columns: TableColumnCtx<T>[], cellIndex: number) => { const getCellClasses = (columns: TableColumnCtx<T>[], cellIndex: number) => {
const column = columns[cellIndex] const column = columns[cellIndex]
const classes = [ const classes = [
'el-table__cell', ns.e('cell'),
column.id, column.id,
column.align, column.align,
column.labelClassName, column.labelClassName,
...getFixedColumnsClass(cellIndex, column.fixed, props.store), ...getFixedColumnsClass(ns.b(), cellIndex, column.fixed, props.store),
] ]
if (column.className) { if (column.className) {
classes.push(column.className) classes.push(column.className)
} }
if (!column.children) { if (!column.children) {
classes.push('is-leaf') classes.push(ns.is('leaf'))
} }
return classes return classes
} }

View File

@ -1,14 +1,13 @@
import { getCurrentInstance, ref } from 'vue' import { getCurrentInstance, ref, inject } from 'vue'
import { isClient } from '@vueuse/core' import { isClient } from '@vueuse/core'
import { hasClass, addClass, removeClass } from '@element-plus/utils/dom' import { hasClass, addClass, removeClass } from '@element-plus/utils/dom'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableHeaderProps } from '.' import type { TableHeaderProps } from '.'
import type { TableColumnCtx } from '../table-column/defaults' import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
function useEvent<T>(props: TableHeaderProps<T>, emit) { function useEvent<T>(props: TableHeaderProps<T>, emit) {
const instance = getCurrentInstance() const instance = getCurrentInstance()
const parent = instance.parent as Table<T> const parent = inject(TABLE_INJECTION_KEY)
const handleFilterClick = (event: Event) => { const handleFilterClick = (event: Event) => {
event.stopPropagation() event.stopPropagation()
return return
@ -20,11 +19,11 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
} else if (column.filterable && !column.sortable) { } else if (column.filterable && !column.sortable) {
handleFilterClick(event) handleFilterClick(event)
} }
parent.emit('header-click', column, event) parent?.emit('header-click', column, event)
} }
const handleHeaderContextMenu = (event: Event, column: TableColumnCtx<T>) => { const handleHeaderContextMenu = (event: Event, column: TableColumnCtx<T>) => {
parent.emit('header-contextmenu', column, event) parent?.emit('header-contextmenu', column, event)
} }
const draggingColumn = ref(null) const draggingColumn = ref(null)
const dragging = ref(false) const dragging = ref(false)
@ -38,7 +37,7 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
const table = parent const table = parent
emit('set-drag-visible', true) emit('set-drag-visible', true)
const tableEl = table.vnode.el const tableEl = table?.vnode.el
const tableLeft = tableEl.getBoundingClientRect().left const tableLeft = tableEl.getBoundingClientRect().left
const columnEl = instance.vnode.el.querySelector(`th.${column.id}`) const columnEl = instance.vnode.el.querySelector(`th.${column.id}`)
const columnRect = columnEl.getBoundingClientRect() const columnRect = columnEl.getBoundingClientRect()
@ -52,7 +51,7 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
startColumnLeft: columnRect.left - tableLeft, startColumnLeft: columnRect.left - tableLeft,
tableLeft, tableLeft,
} }
const resizeProxy = table.refs.resizeProxy as HTMLElement const resizeProxy = table?.refs.resizeProxy as HTMLElement
resizeProxy.style.left = `${(dragState.value as any).startLeft}px` resizeProxy.style.left = `${(dragState.value as any).startLeft}px`
document.onselectstart = function () { document.onselectstart = function () {
@ -76,7 +75,7 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
const finalLeft = parseInt(resizeProxy.style.left, 10) const finalLeft = parseInt(resizeProxy.style.left, 10)
const columnWidth = finalLeft - startColumnLeft const columnWidth = finalLeft - startColumnLeft
column.width = column.realWidth = columnWidth column.width = column.realWidth = columnWidth
table.emit( table?.emit(
'header-dragend', 'header-dragend',
column.width, column.width,
startLeft - startColumnLeft, startLeft - startColumnLeft,
@ -193,7 +192,7 @@ function useEvent<T>(props: TableHeaderProps<T>, emit) {
states.sortProp.value = sortProp states.sortProp.value = sortProp
states.sortOrder.value = sortOrder states.sortOrder.value = sortOrder
parent.store.commit('changeSortCondition') parent?.store.commit('changeSortCondition')
} }
return { return {

View File

@ -5,17 +5,19 @@ import {
nextTick, nextTick,
ref, ref,
h, h,
inject,
} from 'vue' } from 'vue'
import ElCheckbox from '@element-plus/components/checkbox' import ElCheckbox from '@element-plus/components/checkbox'
import { useNamespace } from '@element-plus/hooks'
import FilterPanel from '../filter-panel.vue' import FilterPanel from '../filter-panel.vue'
import useLayoutObserver from '../layout-observer' import useLayoutObserver from '../layout-observer'
import { hColgroup } from '../h-helper' import { hColgroup } from '../h-helper'
import { TABLE_INJECTION_KEY } from '../tokens'
import useEvent from './event-helper' import useEvent from './event-helper'
import useStyle from './style.helper' import useStyle from './style.helper'
import useUtils from './utils-helper' import useUtils from './utils-helper'
import type { ComponentInternalInstance, Ref, PropType } from 'vue' import type { ComponentInternalInstance, Ref, PropType } from 'vue'
import type { DefaultRow, Sort, Table } from '../table/defaults' import type { DefaultRow, Sort } from '../table/defaults'
import type { Store } from '../store' import type { Store } from '../store'
export interface TableHeader extends ComponentInternalInstance { export interface TableHeader extends ComponentInternalInstance {
state: { state: {
@ -58,15 +60,16 @@ export default defineComponent({
}, },
setup(props, { emit }) { setup(props, { emit }) {
const instance = getCurrentInstance() as TableHeader const instance = getCurrentInstance() as TableHeader
const parent = instance.parent as Table<unknown> const parent = inject(TABLE_INJECTION_KEY)
const storeData = parent.store.states const ns = useNamespace('table')
const storeData = parent?.store.states
const filterPanels = ref({}) const filterPanels = ref({})
const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent) const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent!)
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
const { prop, order } = props.defaultSort const { prop, order } = props.defaultSort
const init = true const init = true
parent.store.commit('sort', { prop, order, init }) parent?.store.commit('sort', { prop, order, init })
}) })
}) })
const { const {
@ -96,6 +99,7 @@ export default defineComponent({
instance.filterPanels = filterPanels instance.filterPanels = filterPanels
return { return {
ns,
columns: storeData.columns, columns: storeData.columns,
filterPanels, filterPanels,
onColumnsChange, onColumnsChange,
@ -118,6 +122,7 @@ export default defineComponent({
}, },
render() { render() {
const { const {
ns,
columns, columns,
isGroup, isGroup,
columnRows, columnRows,
@ -141,14 +146,14 @@ export default defineComponent({
border: '0', border: '0',
cellpadding: '0', cellpadding: '0',
cellspacing: '0', cellspacing: '0',
class: 'el-table__header', class: ns.e('header'),
}, },
[ [
hColgroup(columns), hColgroup(columns),
h( h(
'thead', 'thead',
{ {
class: { 'is-group': isGroup }, class: { [ns.is('group')]: isGroup },
}, },
columnRows.map((subColumns, rowIndex) => columnRows.map((subColumns, rowIndex) =>
h( h(

View File

@ -1,19 +1,20 @@
import { getCurrentInstance } from 'vue' import { inject } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { import {
getFixedColumnsClass, getFixedColumnsClass,
getFixedColumnOffset, getFixedColumnOffset,
ensurePosition, ensurePosition,
} from '../util' } from '../util'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableColumnCtx } from '../table-column/defaults' import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
import type { TableHeaderProps } from '.' import type { TableHeaderProps } from '.'
function useStyle<T>(props: TableHeaderProps<T>) { function useStyle<T>(props: TableHeaderProps<T>) {
const instance = getCurrentInstance() const parent = inject(TABLE_INJECTION_KEY)
const parent = instance.parent as Table<T> const ns = useNamespace('table')
const getHeaderRowStyle = (rowIndex: number) => { const getHeaderRowStyle = (rowIndex: number) => {
const headerRowStyle = parent.props.headerRowStyle const headerRowStyle = parent?.props.headerRowStyle
if (typeof headerRowStyle === 'function') { if (typeof headerRowStyle === 'function') {
return headerRowStyle.call(null, { rowIndex }) return headerRowStyle.call(null, { rowIndex })
} }
@ -21,8 +22,8 @@ function useStyle<T>(props: TableHeaderProps<T>) {
} }
const getHeaderRowClass = (rowIndex: number): string => { const getHeaderRowClass = (rowIndex: number): string => {
const classes = [] const classes: string[] = []
const headerRowClassName = parent.props.headerRowClassName const headerRowClassName = parent?.props.headerRowClassName
if (typeof headerRowClassName === 'string') { if (typeof headerRowClassName === 'string') {
classes.push(headerRowClassName) classes.push(headerRowClassName)
} else if (typeof headerRowClassName === 'function') { } else if (typeof headerRowClassName === 'function') {
@ -38,7 +39,7 @@ function useStyle<T>(props: TableHeaderProps<T>) {
row: T, row: T,
column: TableColumnCtx<T> column: TableColumnCtx<T>
) => { ) => {
let headerCellStyles = parent.props.headerCellStyle ?? {} let headerCellStyles = parent?.props.headerCellStyle ?? {}
if (typeof headerCellStyles === 'function') { if (typeof headerCellStyles === 'function') {
headerCellStyles = headerCellStyles.call(null, { headerCellStyles = headerCellStyles.call(null, {
rowIndex, rowIndex,
@ -67,6 +68,7 @@ function useStyle<T>(props: TableHeaderProps<T>) {
const fixedClasses = column.isSubColumn const fixedClasses = column.isSubColumn
? [] ? []
: getFixedColumnsClass<T>( : getFixedColumnsClass<T>(
ns.b(),
columnIndex, columnIndex,
column.fixed, column.fixed,
props.store, props.store,
@ -89,7 +91,7 @@ function useStyle<T>(props: TableHeaderProps<T>) {
classes.push('is-sortable') classes.push('is-sortable')
} }
const headerCellClassName = parent.props.headerCellClassName const headerCellClassName = parent?.props.headerCellClassName
if (typeof headerCellClassName === 'string') { if (typeof headerCellClassName === 'string') {
classes.push(headerCellClassName) classes.push(headerCellClassName)
} else if (typeof headerCellClassName === 'function') { } else if (typeof headerCellClassName === 'function') {
@ -103,7 +105,7 @@ function useStyle<T>(props: TableHeaderProps<T>) {
) )
} }
classes.push('el-table__cell') classes.push(ns.e('cell'))
return classes.join(' ') return classes.join(' ')
} }

View File

@ -1,13 +1,12 @@
import { getCurrentInstance, computed } from 'vue' import { computed, inject } from 'vue'
import { TABLE_INJECTION_KEY } from '../tokens'
import type { TableColumnCtx } from '../table-column/defaults' import type { TableColumnCtx } from '../table-column/defaults'
import type { Table } from '../table/defaults'
import type { TableHeaderProps } from '.' import type { TableHeaderProps } from '.'
const getAllColumns = <T>( const getAllColumns = <T>(
columns: TableColumnCtx<T>[] columns: TableColumnCtx<T>[]
): TableColumnCtx<T>[] => { ): TableColumnCtx<T>[] => {
const result = [] const result: TableColumnCtx<T>[] = []
columns.forEach((column) => { columns.forEach((column) => {
if (column.children) { if (column.children) {
result.push(column) result.push(column)
@ -53,7 +52,7 @@ const convertToRows = <T>(
rows.push([]) rows.push([])
} }
const allColumns = getAllColumns(originColumns) const allColumns: TableColumnCtx<T>[] = getAllColumns(originColumns)
allColumns.forEach((column) => { allColumns.forEach((column) => {
if (!column.children) { if (!column.children) {
@ -69,19 +68,20 @@ const convertToRows = <T>(
} }
function useUtils<T>(props: TableHeaderProps<T>) { function useUtils<T>(props: TableHeaderProps<T>) {
const instance = getCurrentInstance() const parent = inject(TABLE_INJECTION_KEY)
const parent = instance.parent as Table<T>
const columnRows = computed(() => { const columnRows = computed(() => {
return convertToRows(props.store.states.originColumns.value) return convertToRows(props.store.states.originColumns.value)
}) })
const isGroup = computed(() => { const isGroup = computed(() => {
const result = columnRows.value.length > 1 const result = columnRows.value.length > 1
if (result) parent.state.isGroup.value = true if (result && parent) {
parent.state.isGroup.value = true
}
return result return result
}) })
const toggleAllSelection = (event: Event) => { const toggleAllSelection = (event: Event) => {
event.stopPropagation() event.stopPropagation()
parent.store.commit('toggleAllSelection') parent?.store.commit('toggleAllSelection')
} }
return { return {
isGroup, isGroup,

View File

@ -136,14 +136,16 @@ class TableLayout<T> {
updateElsHeight() { updateElsHeight() {
if (!this.table.$ready) return nextTick(() => this.updateElsHeight()) if (!this.table.$ready) return nextTick(() => this.updateElsHeight())
const { headerWrapper, appendWrapper, footerWrapper, bodyWrapper } = const {
this.table.refs headerWrapper,
appendWrapper,
footerWrapper,
tableHeader,
tableBody,
} = this.table.refs
this.appendHeight.value = appendWrapper ? appendWrapper.offsetHeight : 0 this.appendHeight.value = appendWrapper ? appendWrapper.offsetHeight : 0
if (this.showHeader && !headerWrapper) return if (this.showHeader && !headerWrapper) return
const headerTrElm: HTMLElement = tableHeader ? tableHeader.$el : null
const headerTrElm: HTMLElement = headerWrapper
? headerWrapper.querySelector('.el-table__header tr')
: null
const noneHeader = this.headerDisplayNone(headerTrElm) const noneHeader = this.headerDisplayNone(headerTrElm)
const headerHeight = (this.headerHeight.value = !this.showHeader const headerHeight = (this.headerHeight.value = !this.showHeader
@ -159,7 +161,7 @@ class TableLayout<T> {
return nextTick(() => this.updateElsHeight()) return nextTick(() => this.updateElsHeight())
} }
const tableHeight = (this.tableHeight.value = const tableHeight = (this.tableHeight.value =
this.table.vnode.el.clientHeight) this.table?.vnode.el?.clientHeight)
const footerHeight = (this.footerHeight.value = footerWrapper const footerHeight = (this.footerHeight.value = footerWrapper
? footerWrapper.offsetHeight ? footerWrapper.offsetHeight
: 0) : 0)
@ -169,8 +171,7 @@ class TableLayout<T> {
} }
this.bodyHeight.value = this.bodyHeight.value =
tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0) tableHeight - headerHeight - footerHeight + (footerWrapper ? 1 : 0)
this.bodyScrollHeight.value = this.bodyScrollHeight.value = tableBody?.$el.scrollHeight!
bodyWrapper.querySelector('.el-table__body')?.scrollHeight!
} }
this.fixedBodyHeight.value = this.scrollX.value this.fixedBodyHeight.value = this.scrollX.value
? this.bodyHeight.value - this.gutterWidth ? this.bodyHeight.value - this.gutterWidth

View File

@ -3,28 +3,29 @@
ref="tableWrapper" ref="tableWrapper"
:class="[ :class="[
{ {
'el-table--fit': fit, [ns.m('fit')]: fit,
'el-table--striped': stripe, [ns.m('striped')]: stripe,
'el-table--border': border || isGroup, [ns.m('border')]: border || isGroup,
'el-table--hidden': isHidden, [ns.m('hidden')]: isHidden,
'el-table--group': isGroup, [ns.m('group')]: isGroup,
'el-table--fluid-height': maxHeight, [ns.m('fluid-height')]: maxHeight,
'el-table--scrollable-x': layout.scrollX.value, [ns.m('scrollable-x')]: layout.scrollX.value,
'el-table--scrollable-y': layout.scrollY.value, [ns.m('scrollable-y')]: layout.scrollY.value,
'el-table--enable-row-hover': !store.states.isComplex.value, [ns.m('enable-row-hover')]: !store.states.isComplex.value,
'el-table--enable-row-transition': [ns.m('enable-row-transition')]:
(store.states.data.value || []).length !== 0 && (store.states.data.value || []).length !== 0 &&
(store.states.data.value || []).length < 100, (store.states.data.value || []).length < 100,
'has-footer': showSummary, 'has-footer': showSummary,
}, },
tableSize ? `el-table--${tableSize}` : '', ns.m(tableSize),
className, className,
'el-table', ns.b(),
]" ]"
:style="style" :style="style"
:data-prefix="ns.namespace.value"
@mouseleave="handleMouseLeave()" @mouseleave="handleMouseLeave()"
> >
<div class="el-table__inner-wrapper"> <div :class="ns.e('inner-wrapper')">
<div ref="hiddenColumns" class="hidden-columns"> <div ref="hiddenColumns" class="hidden-columns">
<slot></slot> <slot></slot>
</div> </div>
@ -32,7 +33,7 @@
v-if="showHeader" v-if="showHeader"
ref="headerWrapper" ref="headerWrapper"
v-mousewheel="handleHeaderFooterMousewheel" v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__header-wrapper" :class="ns.e('header-wrapper')"
> >
<table-header <table-header
ref="tableHeader" ref="tableHeader"
@ -43,9 +44,10 @@
@set-drag-visible="setDragVisible" @set-drag-visible="setDragVisible"
/> />
</div> </div>
<div ref="bodyWrapper" :style="bodyHeight" class="el-table__body-wrapper"> <div ref="bodyWrapper" :style="bodyHeight" :class="ns.e('body-wrapper')">
<el-scrollbar ref="scrollWrapper" :height="height"> <el-scrollbar ref="scrollWrapper" :height="height">
<table-body <table-body
ref="tableBody"
:context="context" :context="context"
:highlight="highlightCurrentRow" :highlight="highlightCurrentRow"
:row-class-name="rowClassName" :row-class-name="rowClassName"
@ -61,29 +63,29 @@
v-if="isEmpty" v-if="isEmpty"
ref="emptyBlock" ref="emptyBlock"
:style="emptyBlockStyle" :style="emptyBlockStyle"
class="el-table__empty-block" :class="ns.e('empty-block')"
> >
<span class="el-table__empty-text"> <span :class="ns.e('empty-text')">
<slot name="empty">{{ computedEmptyText }}</slot> <slot name="empty">{{ computedEmptyText }}</slot>
</span> </span>
</div> </div>
<div <div
v-if="$slots.append" v-if="$slots.append"
ref="appendWrapper" ref="appendWrapper"
class="el-table__append-wrapper" :class="ns.e('append-wrapper')"
> >
<slot name="append"></slot> <slot name="append"></slot>
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
<div v-if="border || isGroup" class="el-table__border-left-patch"></div> <div v-if="border || isGroup" :class="ns.e('border-left-patch')"></div>
</div> </div>
<div <div
v-if="showSummary" v-if="showSummary"
v-show="!isEmpty" v-show="!isEmpty"
ref="footerWrapper" ref="footerWrapper"
v-mousewheel="handleHeaderFooterMousewheel" v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper" :class="ns.e('footer-wrapper')"
> >
<table-footer <table-footer
:border="border" :border="border"
@ -97,7 +99,7 @@
<div <div
v-show="resizeProxyVisible" v-show="resizeProxyVisible"
ref="resizeProxy" ref="resizeProxy"
class="el-table__column-resize-proxy" :class="ns.e('column-resize-proxy')"
></div> ></div>
</div> </div>
</template> </template>
@ -106,7 +108,7 @@
import { defineComponent, getCurrentInstance, computed, provide } from 'vue' import { defineComponent, getCurrentInstance, computed, provide } from 'vue'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { Mousewheel } from '@element-plus/directives' import { Mousewheel } from '@element-plus/directives'
import { useLocale } from '@element-plus/hooks' import { useLocale, useNamespace } from '@element-plus/hooks'
import ElScrollbar from '@element-plus/components/scrollbar' import ElScrollbar from '@element-plus/components/scrollbar'
import { createStore } from './store/helper' import { createStore } from './store/helper'
import TableLayout from './table-layout' import TableLayout from './table-layout'
@ -156,6 +158,7 @@ export default defineComponent({
setup(props) { setup(props) {
type Row = typeof props.data[number] type Row = typeof props.data[number]
const { t } = useLocale() const { t } = useLocale()
const ns = useNamespace('table')
const table = getCurrentInstance() as Table<Row> const table = getCurrentInstance() as Table<Row>
provide(TABLE_INJECTION_KEY, table) provide(TABLE_INJECTION_KEY, table)
const store = createStore<Row>(table, props) const store = createStore<Row>(table, props)
@ -223,6 +226,7 @@ export default defineComponent({
}) })
return { return {
ns,
layout, layout,
store, store,
handleHeaderFooterMousewheel, handleHeaderFooterMousewheel,

View File

@ -136,9 +136,12 @@ export const getColumnByCell = function <T>(
table: { table: {
columns: TableColumnCtx<T>[] columns: TableColumnCtx<T>[]
}, },
cell: HTMLElement cell: HTMLElement,
namespace: string
): null | TableColumnCtx<T> { ): null | TableColumnCtx<T> {
const matches = (cell.className || '').match(/el-table_[^\s]+/gm) const matches = (cell.className || '').match(
new RegExp(`${namespace}-table_[^\\s]+`, 'gm')
)
if (matches) { if (matches) {
return getColumnById(table, matches[0]) return getColumnById(table, matches[0])
} }
@ -426,6 +429,7 @@ export const isFixedColumn = <T>(
} }
export const getFixedColumnsClass = <T>( export const getFixedColumnsClass = <T>(
namespace: string,
index: number, index: number,
fixed: string | boolean, fixed: string | boolean,
store: any, store: any,
@ -435,7 +439,7 @@ export const getFixedColumnsClass = <T>(
const { direction, start } = isFixedColumn(index, fixed, store, realColumns) const { direction, start } = isFixedColumn(index, fixed, store, realColumns)
if (direction) { if (direction) {
const isLeft = direction === 'left' const isLeft = direction === 'left'
classes.push(`el-table-fixed-column--${direction}`) classes.push(`${namespace}-fixed-column--${direction}`)
if (isLeft && start === store.states.fixedLeafColumnsLength.value - 1) { if (isLeft && start === store.states.fixedLeafColumnsLength.value - 1) {
classes.push('is-last-column') classes.push('is-last-column')
} else if ( } else if (