vxe-table
Version:
A PC-end table component based on Vxe UI, supporting copy-paste, data pivot table, and high-performance virtual list table solution.
369 lines (344 loc) • 15.1 kB
text/typescript
import { h, ref, Ref, PropType, inject, nextTick, onMounted, onUnmounted } from 'vue'
import { defineVxeComponent } from '../../ui/src/comp'
import XEUtils from 'xe-utils'
import { VxeUI } from '../../ui'
import { updateCellTitle, getPropClass } from '../../ui/src/dom'
import { getCalcHeight } from './util'
import type { VxeTablePrivateMethods, VxeTableConstructor, VxeTableMethods, VxeTableDefines } from '../../../types'
const { renderer, renderEmptyElement } = VxeUI
const sourceType = 'table'
const renderType = 'footer'
export default defineVxeComponent({
name: 'VxeTableFooter',
props: {
footerTableData: {
type: Array as PropType<any[][]>,
default: () => []
},
tableColumn: {
type: Array as PropType<VxeTableDefines.ColumnInfo[]>,
default: () => []
},
fixedColumn: {
type: Array as PropType<VxeTableDefines.ColumnInfo[]>,
default: () => []
},
fixedType: {
type: String as PropType<'right' | 'left' | ''>,
default: null
}
},
setup (props) {
const $xeTable = inject('$xeTable', {} as VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods)
const { xID, props: tableProps, reactData: tableReactData, internalData: tableInternalData } = $xeTable
const { computeFooterTooltipOpts, computeColumnOpts, computeCellOpts, computeFooterCellOpts, computeDefaultRowHeight, computeResizableOpts, computeVirtualXOpts, computeIsFooterRenderOptimize } = $xeTable.getComputeMaps()
const refElem = ref() as Ref<HTMLDivElement>
const refFooterScroll = ref() as Ref<HTMLDivElement>
const refFooterTable = ref() as Ref<HTMLTableElement>
const refFooterColgroup = ref() as Ref<HTMLTableColElement>
const refFooterTFoot = ref() as Ref<HTMLTableSectionElement>
const refFooterXSpace = ref() as Ref<HTMLDivElement>
const renderRows = (isOptimizeMode: boolean, tableColumn: VxeTableDefines.ColumnInfo[], footerTableData: any[], row: any, $rowIndex: number, _rowIndex: number) => {
const $xeGrid = $xeTable.xeGrid
const $xeGantt = $xeTable.xeGantt
const { fixedType } = props
const { resizable: allResizable, border, footerCellClassName, footerCellStyle, footerAlign: allFooterAlign, footerSpanMethod, align: allAlign, columnKey, showFooterOverflow: allColumnFooterOverflow } = tableProps
const { scrollXLoad, scrollYLoad, overflowX, currentColumn, mergeFootFlag } = tableReactData
const { fullColumnIdData, mergeFooterList, mergeFooterCellMaps, scrollXStore } = tableInternalData
const virtualXOpts = computeVirtualXOpts.value
const footerTooltipOpts = computeFooterTooltipOpts.value
const resizableOpts = computeResizableOpts.value
const { isAllColumnDrag } = resizableOpts
const columnOpts = computeColumnOpts.value
const defaultRowHeight = computeDefaultRowHeight.value
const cellOpts = computeCellOpts.value
const footerCellOpts = computeFooterCellOpts.value
const currCellHeight = getCalcHeight(footerCellOpts.height) || defaultRowHeight
return tableColumn.map((column, $columnIndex) => {
const { type, showFooterOverflow, footerAlign, align, footerClassName, editRender, cellRender } = column
const colid = column.id
const colRest = fullColumnIdData[colid] || {}
const renderOpts = editRender || cellRender
const compConf = renderOpts ? renderer.get(renderOpts.name) : null
const showAllTip = footerTooltipOpts.showAll
const fixedHiddenColumn = overflowX && (fixedType ? column.fixed !== fixedType : !!column.fixed)
const isPadding = XEUtils.isBoolean(footerCellOpts.padding) ? footerCellOpts.padding : cellOpts.padding
const footOverflow = XEUtils.eqNull(showFooterOverflow) ? allColumnFooterOverflow : showFooterOverflow
const footAlign = footerAlign || (compConf ? compConf.tableFooterCellAlign : '') || allFooterAlign || align || (compConf ? compConf.tableCellAlign : '') || allAlign
const showEllipsis = footOverflow === 'ellipsis'
const showTitle = footOverflow === 'title'
const showTooltip = footOverflow === true || footOverflow === 'tooltip'
const hasEllipsis = showTitle || showTooltip || showEllipsis
const showResizable = (XEUtils.isBoolean(column.resizable) ? column.resizable : (columnOpts.resizable || allResizable))
const attrs: any = { colid }
const tfOns: any = {}
const columnIndex = colRest.index
const _columnIndex = colRest._index
const itemIndex = _columnIndex
const cellParams: VxeTableDefines.CellRenderFooterParams & {
$table: VxeTableConstructor<any> & VxeTablePrivateMethods
} = {
$table: $xeTable,
$grid: $xeGrid,
$gantt: $xeGantt,
row,
rowIndex: _rowIndex,
_rowIndex,
$rowIndex,
column,
columnIndex,
$columnIndex,
_columnIndex,
itemIndex,
items: row,
fixed: fixedType,
source: sourceType,
type: renderType,
data: footerTableData
}
if (showTitle || showTooltip || showAllTip) {
tfOns.onMouseenter = (evnt: MouseEvent) => {
if (showTitle) {
updateCellTitle(evnt.currentTarget, column)
} else if (showTooltip || showAllTip) {
$xeTable.triggerFooterTooltipEvent(evnt, cellParams)
}
}
}
if (showTooltip || showAllTip) {
tfOns.onMouseleave = (evnt: MouseEvent) => {
if (showTooltip || showAllTip) {
$xeTable.handleTargetLeaveEvent(evnt)
}
}
}
tfOns.onClick = (evnt: MouseEvent) => {
$xeTable.dispatchEvent('footer-cell-click', Object.assign({ cell: evnt.currentTarget }, cellParams), evnt)
}
tfOns.onDblclick = (evnt: MouseEvent) => {
$xeTable.dispatchEvent('footer-cell-dblclick', Object.assign({ cell: evnt.currentTarget }, cellParams), evnt)
}
let isMergeCell = false
// 合并行或列
if (mergeFootFlag && mergeFooterList.length) {
const spanRest = mergeFooterCellMaps[`${_rowIndex}:${_columnIndex}`]
if (spanRest) {
const { rowspan, colspan } = spanRest
if (!rowspan || !colspan) {
return null
}
if (rowspan > 1) {
isMergeCell = true
attrs.rowspan = rowspan
}
if (colspan > 1) {
isMergeCell = true
attrs.colspan = colspan
}
}
} else if (footerSpanMethod) {
// 自定义合并方法
const { rowspan = 1, colspan = 1 } = footerSpanMethod(cellParams) || {}
if (!rowspan || !colspan) {
return null
}
if (rowspan > 1) {
attrs.rowspan = rowspan
}
if (colspan > 1) {
attrs.colspan = colspan
}
}
const isLastColumn = $columnIndex === tableColumn.length - 1
const isAutoCellWidth = !column.resizeWidth && (column.minWidth === 'auto' || column.width === 'auto')
let isVNPreEmptyStatus = false
if (isOptimizeMode && overflowX && !isMergeCell) {
if (scrollXLoad && tableColumn.length > 10 && !column.fixed && !virtualXOpts.immediate && (_columnIndex < scrollXStore.visibleStartIndex - scrollXStore.preloadSize || _columnIndex > scrollXStore.visibleEndIndex + scrollXStore.preloadSize)) {
isVNPreEmptyStatus = true
}
}
const tcStyle: Record<string, string> = {}
if (hasEllipsis) {
tcStyle.height = `${currCellHeight}px`
} else {
tcStyle.minHeight = `${currCellHeight}px`
}
return h('td', {
class: ['vxe-table--column vxe-footer--column', column.id, {
[`col--${footAlign}`]: footAlign,
[`col--${type}`]: type,
'col--last': isLastColumn,
'fixed--width': !isAutoCellWidth,
'fixed--hidden': fixedHiddenColumn,
'is--padding': isPadding,
'col--ellipsis': hasEllipsis,
'col--current': currentColumn === column
}, getPropClass(footerClassName, cellParams), getPropClass(footerCellClassName, cellParams)],
...attrs,
style: footerCellStyle ? (XEUtils.isFunction(footerCellStyle) ? footerCellStyle(cellParams) : footerCellStyle) : null,
...tfOns,
key: columnKey || scrollXLoad || scrollYLoad || columnOpts.useKey || columnOpts.drag ? column.id : $columnIndex
}, [
h('div', {
class: ['vxe-cell', {
'c--title': showTitle,
'c--tooltip': showTooltip,
'c--ellipsis': showEllipsis
}],
style: tcStyle
}, isVNPreEmptyStatus
? []
: [
h('div', {
colid,
class: 'vxe-cell--wrapper vxe-footer-cell--wrapper'
}, column.renderFooter(cellParams))
]),
/**
* 列宽拖动
*/
!fixedHiddenColumn && showResizable && isAllColumnDrag
? h('div', {
class: ['vxe-cell--col-resizable', {
'is--line': !border || border === 'none'
}],
onMousedown: (evnt: MouseEvent) => $xeTable.handleColResizeMousedownEvent(evnt, fixedType, cellParams),
onDblclick: (evnt: MouseEvent) => $xeTable.handleColResizeDblclickEvent(evnt, cellParams)
})
: renderEmptyElement($xeTable)
])
})
}
const renderHeads = (isOptimizeMode: boolean, renderColumnList: VxeTableDefines.ColumnInfo[]) => {
const { fixedType, footerTableData } = props
const { footerRowClassName, footerRowStyle } = tableProps
return footerTableData.map((row, $rowIndex) => {
const _rowIndex = $rowIndex
const rowParams = { $table: $xeTable, row, _rowIndex, $rowIndex, fixed: fixedType, type: renderType }
return h('tr', {
key: $rowIndex,
class: [
'vxe-footer--row',
footerRowClassName ? XEUtils.isFunction(footerRowClassName) ? footerRowClassName(rowParams) : footerRowClassName : ''
],
style: footerRowStyle ? (XEUtils.isFunction(footerRowStyle) ? footerRowStyle(rowParams) : footerRowStyle) : null
}, renderRows(isOptimizeMode, renderColumnList, footerTableData, row, $rowIndex, _rowIndex))
})
}
const renderVN = () => {
const { fixedType, fixedColumn, tableColumn } = props
const { visibleColumn, fullColumnIdData } = tableInternalData
const { isGroup, isColLoading, overflowX, scrollXLoad, dragCol } = tableReactData
const isFooterRenderOptimize = computeIsFooterRenderOptimize.value
let renderColumnList = tableColumn
const isOptimizeMode = isFooterRenderOptimize
if (!isOptimizeMode || (!isColLoading && (fixedType || !overflowX))) {
renderColumnList = visibleColumn
}
if (fixedType) {
if (isOptimizeMode) {
renderColumnList = fixedColumn || []
}
}
if (!fixedType && !isGroup) {
// 列拖拽
if (scrollXLoad && dragCol) {
if (renderColumnList.length > 2) {
const dCowRest = fullColumnIdData[dragCol.id]
if (dCowRest) {
const dcIndex = dCowRest._index
const firstCol = renderColumnList[0]
const lastCol = renderColumnList[renderColumnList.length - 1]
const firstColRest = fullColumnIdData[firstCol.id]
const lastColRest = fullColumnIdData[lastCol.id]
if (firstColRest && lastColRest) {
const fcIndex = firstColRest._index
const lcIndex = lastColRest._index
if (dcIndex < fcIndex) {
renderColumnList = [dragCol].concat(renderColumnList)
} else if (dcIndex > lcIndex) {
renderColumnList = renderColumnList.concat([dragCol])
}
}
}
}
}
}
return h('div', {
ref: refElem,
class: ['vxe-table--footer-wrapper', fixedType ? `fixed-${fixedType}--wrapper` : 'body--wrapper'],
xid: xID
}, [
h('div', {
ref: refFooterScroll,
class: 'vxe-table--footer-inner-wrapper',
onScroll (evnt) {
$xeTable.triggerFooterScrollEvent(evnt, fixedType)
}
}, [
fixedType
? renderEmptyElement($xeTable)
: h('div', {
ref: refFooterXSpace,
class: 'vxe-body--x-space'
}),
h('table', {
ref: refFooterTable,
class: 'vxe-table--footer',
xid: xID,
cellspacing: 0,
cellpadding: 0,
border: 0,
xvm: isOptimizeMode ? '1' : null
}, [
/**
* 列宽
*/
h('colgroup', {
ref: refFooterColgroup
}, renderColumnList.map((column, $columnIndex) => {
return h('col', {
name: column.id,
key: $columnIndex,
style: {
width: `${column.renderWidth}px`
}
})
})),
/**
* 底部
*/
h('tfoot', {
ref: refFooterTFoot
}, renderHeads(isOptimizeMode, renderColumnList))
])
])
])
}
onMounted(() => {
nextTick(() => {
const { fixedType } = props
const { elemStore } = tableInternalData
const prefix = `${fixedType || 'main'}-footer-`
elemStore[`${prefix}wrapper`] = refElem
elemStore[`${prefix}scroll`] = refFooterScroll
elemStore[`${prefix}table`] = refFooterTable
elemStore[`${prefix}colgroup`] = refFooterColgroup
elemStore[`${prefix}list`] = refFooterTFoot
elemStore[`${prefix}xSpace`] = refFooterXSpace
})
})
onUnmounted(() => {
const { fixedType } = props
const { elemStore } = tableInternalData
const prefix = `${fixedType || 'main'}-footer-`
elemStore[`${prefix}wrapper`] = null
elemStore[`${prefix}scroll`] = null
elemStore[`${prefix}table`] = null
elemStore[`${prefix}colgroup`] = null
elemStore[`${prefix}list`] = null
elemStore[`${prefix}xSpace`] = null
})
return renderVN
}
})