vxe-table
Version:
一个基于 vue 的 PC 端表格组件,支持增删改查、虚拟树、拖拽排序,懒加载、快捷菜单、数据校验、树形结构、打印、导入导出、自定义模板、渲染器、JSON 配置式...
1,427 lines (1,312 loc) • 427 kB
text/typescript
import { defineComponent, h, ComponentPublicInstance, reactive, ref, Ref, provide, inject, nextTick, onActivated, onDeactivated, onBeforeUnmount, onUnmounted, watch, computed, onMounted } from 'vue'
import XEUtils from 'xe-utils'
import { initTpImg, getTpImg, isPx, isScale, hasClass, addClass, removeClass, getEventTargetNode, getPaddingTopBottomSize, getOffsetPos, setScrollTop, setScrollLeft, toCssUnit } from '../../ui/src/dom'
import { getLastZIndex, nextZIndex, hasChildrenList, getFuncText, isEnableConf, formatText, eqEmptyValue } from '../../ui/src/utils'
import { VxeUI } from '../../ui'
import Cell from './cell'
import TableBodyComponent from './body'
import TableHeaderComponent from './header'
import TableFooterComponent from './footer'
import tableProps from './props'
import tableEmits from './emits'
import { getRowUniqueId, clearTableAllStatus, getRowkey, getRowid, rowToVisible, colToVisible, getCellValue, setCellValue, handleRowidOrRow, handleFieldOrColumn, toTreePathSeq, restoreScrollLocation, getRootColumn, getRefElem, getColReMinWidth, createHandleUpdateRowId, createHandleGetRowId } from './util'
import { getSlotVNs } from '../../ui/src/vn'
import { warnLog, errLog } from '../../ui/src/log'
import TableCustomPanelComponent from '../module/custom/panel'
import TableFilterPanelComponent from '../module/filter/panel'
import TableImportPanelComponent from '../module/export/import-panel'
import TableExportPanelComponent from '../module/export/export-panel'
import TableMenuPanelComponent from '../module/menu/panel'
import type { VxeLoadingComponent, VxeTooltipInstance, VxeTooltipComponent, VxeTabsConstructor, VxeTabsPrivateMethods, ValueOf, VxeComponentSlotType } from 'vxe-pc-ui'
import type { VxeGridConstructor, VxeGridPrivateMethods, VxeTableConstructor, TableReactData, TableInternalData, VxeTablePropTypes, VxeToolbarConstructor, TablePrivateMethods, VxeTablePrivateRef, VxeTablePrivateComputed, VxeTablePrivateMethods, TableMethods, VxeTableMethods, VxeTableDefines, VxeTableEmits, VxeTableProps, VxeColumnPropTypes } from '../../../types'
const { getConfig, getIcon, getI18n, renderer, formats, createEvent, globalResize, interceptor, hooks, globalEvents, GLOBAL_EVENT_KEYS, useFns, renderEmptyElement } = VxeUI
const supportMaxRow = 5e6
const customStorageKey = 'VXE_CUSTOM_STORE'
const maxYHeight = 5e6
const maxXWidth = 5e6
export default defineComponent({
name: 'VxeTable',
props: tableProps,
emits: tableEmits,
setup (props, context) {
const { slots, emit } = context
const xID = XEUtils.uniqueId()
const browseObj = XEUtils.browse()
// 使用已安装的组件,如果未安装则不渲染
const VxeUILoadingComponent = VxeUI.getComponent<VxeLoadingComponent>('VxeLoading')
const VxeUITooltipComponent = VxeUI.getComponent<VxeTooltipComponent>('VxeTooltip')
const $xeTabs = inject<(VxeTabsConstructor & VxeTabsPrivateMethods) | null>('$xeTabs', null)
const { computeSize } = useFns.useSize(props)
const reactData = reactive<TableReactData>({
// 低性能的静态列
staticColumns: [],
// 渲染的列分组
tableGroupColumn: [],
// 可视区渲染的列
tableColumn: [],
// 渲染中的数据
tableData: [],
// 是否启用了横向 X 可视渲染方式加载
scrollXLoad: false,
// 是否启用了纵向 Y 可视渲染方式加载
scrollYLoad: false,
// 是否存在纵向滚动条
overflowY: true,
// 是否存在横向滚动条
overflowX: false,
// 纵向滚动条的宽度
scrollbarWidth: 0,
// 横向滚动条的高度
scrollbarHeight: 0,
// 最后滚动时间戳
lastScrollTime: 0,
// 行高
rowHeight: 0,
// 表格父容器的高度
parentHeight: 0,
// 是否使用分组表头
isGroup: false,
isAllOverflow: false,
// 复选框属性,是否全选
isAllSelected: false,
// 复选框属性,有选中且非全选状态
isIndeterminate: false,
// 当前行
currentRow: null,
// 单选框属性,选中列
currentColumn: null,
// 单选框属性,选中行
selectRadioRow: null,
// 表尾合计数据
footerTableData: [],
// 展开列信息
expandColumn: null,
// 树节点列信息
treeNodeColumn: null,
hasFixedColumn: false,
// 刷新列标识,当列筛选被改变时,触发表格刷新数据
upDataFlag: 0,
// 刷新列标识,当列的特定属性被改变时,触发表格刷新列
reColumnFlag: 0,
// 初始化标识
initStore: {
filter: false,
import: false,
export: false,
custom: false
},
// 自定义列相关的信息
customStore: {
btnEl: null,
isAll: false,
isIndeterminate: false,
activeBtn: false,
activeWrapper: false,
visible: false,
maxHeight: 0,
oldSortMaps: {},
oldFixedMaps: {},
oldVisibleMaps: {}
},
customColumnList: [],
// 当前选中的筛选列
filterStore: {
isAllSelected: false,
isIndeterminate: false,
style: null,
options: [],
column: null,
multiple: false,
visible: false,
maxHeight: null
},
// 存放列相关的信息
columnStore: {
leftList: [],
centerList: [],
rightList: [],
resizeList: [],
pxList: [],
pxMinList: [],
autoMinList: [],
scaleList: [],
scaleMinList: [],
autoList: [],
remainList: []
},
// 存放快捷菜单的信息
ctxMenuStore: {
selected: null,
visible: false,
showChild: false,
selectChild: null,
list: [],
style: null
},
// 存放可编辑相关信息
editStore: {
indexs: {
columns: []
},
titles: {
columns: []
},
// 选中源
selected: {
row: null,
column: null
},
// 已复制源
copyed: {
cut: false,
rows: [],
columns: []
},
// 激活
actived: {
row: null,
column: null
},
// 当前被强制聚焦单元格,只会在鼠标点击后算聚焦
focused: {
row: null,
column: null
}
},
// 存放 tooltip 相关信息
tooltipStore: {
row: null,
column: null,
content: null,
visible: false,
currOpts: {}
},
// 存放数据校验相关信息
validStore: {
visible: false
},
validErrorMaps: {},
// 导入相关信息
importStore: {
inited: false,
file: null,
type: '',
modeList: [],
typeList: [],
filename: '',
visible: false
},
importParams: {
mode: '',
types: null,
message: true
},
// 导出相关信息
exportStore: {
inited: false,
name: '',
modeList: [],
typeList: [],
columns: [],
isPrint: false,
hasFooter: false,
hasMerge: false,
hasTree: false,
hasColgroup: false,
visible: false
},
exportParams: {
filename: '',
sheetName: '',
mode: '',
type: '',
isColgroup: false,
isMerge: false,
isAllExpand: false,
useStyle: false,
original: false,
message: true,
isHeader: false,
isTitle: false,
isFooter: false
},
rowExpandedFlag: 1,
treeExpandedFlag: 1,
updateCheckboxFlag: 1,
pendingRowFlag: 1,
insertRowFlag: 1,
removeRowFlag: 1,
mergeBodyFlag: 1,
mergeFootFlag: 1,
rowHeightStore: {
large: 52,
default: 48,
medium: 44,
small: 40,
mini: 36
},
scrollVMLoading: false,
scrollYHeight: 0,
scrollYTop: 0,
isScrollYBig: false,
scrollXLeft: 0,
scrollXWidth: 0,
isScrollXBig: false,
rowExpandHeightFlag: 1,
calcCellHeightFlag: 1,
resizeHeightFlag: 1,
resizeWidthFlag: 1,
isCustomStatus: false,
isDragRowMove: false,
dragRow: null,
isDragColMove: false,
dragCol: null,
dragTipText: '',
isDragResize: false,
isRowLoading: false,
isColLoading: false
})
const internalData: TableInternalData = {
tZindex: 0,
elemStore: {},
// 存放横向 X 虚拟滚动相关的信息
scrollXStore: {
preloadSize: 0,
offsetSize: 0,
visibleSize: 0,
visibleStartIndex: 0,
visibleEndIndex: 0,
startIndex: 0,
endIndex: 0
},
// 存放纵向 Y 虚拟滚动相关信息
scrollYStore: {
preloadSize: 0,
offsetSize: 0,
visibleSize: 0,
visibleStartIndex: 0,
visibleEndIndex: 0,
startIndex: 0,
endIndex: 0
},
// 表格宽度
tableWidth: 0,
// 表格高度
tableHeight: 0,
// 表头高度
headerHeight: 0,
// 表尾高度
footerHeight: 0,
customHeight: 0,
customMinHeight: 0,
customMaxHeight: 0,
// 当前 hover 行
hoverRow: null,
// 最后滚动位置
lastScrollLeft: 0,
lastScrollTop: 0,
// 单选框属性,已选中保留的行
radioReserveRow: null,
// 复选框属性,已选中保留的行集合
checkboxReserveRowMap: {},
// 行数据,已展开保留的行集合
rowExpandedReserveRowMap: {},
// 树结构数据,已展开保留的行集合
treeExpandedReserveRowMap: {},
// 树结构数据,不确定状态的集合
treeIndeterminateRowMaps: {},
// 列表完整数据、条件处理后
tableFullData: [],
afterFullData: [],
afterTreeFullData: [],
// 列表条件处理后数据集合
afterFullRowMaps: {},
// 树结构完整数据、条件处理后
tableFullTreeData: [],
tableSynchData: [],
tableSourceData: [],
// 收集的列配置(带分组)
collectColumn: [],
// 完整所有列(不带分组)
tableFullColumn: [],
// 渲染所有列
visibleColumn: [],
// 全量数据集(包括当前和已删除)
fullAllDataRowIdData: {},
// 数据集(仅当前)
fullDataRowIdData: {},
// 数据集(仅可视)
visibleDataRowIdData: {},
// 渲染中缓存数据
sourceDataRowIdData: {},
fullColumnIdData: {},
fullColumnFieldData: {},
// 合并单元格的数据
mergeBodyList: [],
mergeBodyMaps: {},
// 合并表尾的数据
mergeFooterList: [],
mergeFooterMaps: {},
// 已合并单元格数据集合
mergeBodyCellMaps: {},
// 已合并表尾数据集合
mergeFooterCellMaps: {},
// 已展开的行集合
rowExpandedMaps: {},
// 懒加载中的展开行的集合
rowExpandLazyLoadedMaps: {},
// 已展开树节点集合
treeExpandedMaps: {},
// 懒加载中的树节点的集合
treeExpandLazyLoadedMaps: {},
// 复选框属性,已选中的行集合
selectCheckboxMaps: {},
// 已标记的对象集
pendingRowMaps: {},
// 已新增的临时行
insertRowMaps: {},
// 已删除行
removeRowMaps: {},
inited: false,
tooltipTimeout: null,
initStatus: false,
isActivated: false
}
let tableMethods = {} as TableMethods
let tablePrivateMethods = {} as TablePrivateMethods
const refElem = ref() as Ref<HTMLDivElement>
const refVarElem = ref() as Ref<HTMLDivElement>
const refTooltip = ref() as Ref<VxeTooltipInstance>
const refCommTooltip = ref() as Ref<VxeTooltipInstance>
const refValidTooltip = ref() as Ref<VxeTooltipInstance>
const refTableMenu = ref() as Ref<any>
const refTableFilter = ref() as Ref<any>
const refTableCustom = ref() as Ref<ComponentPublicInstance>
const refTableViewportElem = ref<HTMLDivElement>()
const refTableHeader = ref() as Ref<ComponentPublicInstance>
const refTableBody = ref() as Ref<ComponentPublicInstance>
const refTableFooter = ref() as Ref<ComponentPublicInstance>
const refTableLeftHeader = ref() as Ref<ComponentPublicInstance>
const refTableLeftBody = ref() as Ref<ComponentPublicInstance>
const refTableLeftFooter = ref() as Ref<ComponentPublicInstance>
const refTableRightHeader = ref() as Ref<ComponentPublicInstance>
const refTableRightBody = ref() as Ref<ComponentPublicInstance>
const refTableRightFooter = ref() as Ref<ComponentPublicInstance>
const refLeftContainer = ref() as Ref<HTMLDivElement>
const refRightContainer = ref() as Ref<HTMLDivElement>
const refColResizeBar = ref() as Ref<HTMLDivElement>
const refRowResizeBar = ref() as Ref<HTMLDivElement>
const refEmptyPlaceholder = ref() as Ref<HTMLDivElement>
const refDragTipElem = ref<HTMLDivElement>()
const refDragRowLineElem = ref<HTMLDivElement>()
const refDragColLineElem = ref<HTMLDivElement>()
const refRowExpandElem = ref<HTMLDivElement>()
const refRowExpandYSpaceElem = ref<HTMLDivElement>()
const refScrollXVirtualElem = ref<HTMLDivElement>()
const refScrollYVirtualElem = ref<HTMLDivElement>()
const refScrollXHandleElem = ref<HTMLDivElement>()
const refScrollXLeftCornerElem = ref<HTMLDivElement>()
const refScrollXRightCornerElem = ref<HTMLDivElement>()
const refScrollYHandleElem = ref<HTMLDivElement>()
const refScrollYTopCornerElem = ref<HTMLDivElement>()
const refScrollXWrapperElem = ref<HTMLDivElement>()
const refScrollYWrapperElem = ref<HTMLDivElement>()
const refScrollYBottomCornerElem = ref<HTMLDivElement>()
const refScrollXSpaceElem = ref<HTMLDivElement>()
const refScrollYSpaceElem = ref<HTMLDivElement>()
const $xeGrid = inject<(VxeGridConstructor & VxeGridPrivateMethods) | null>('$xeGrid', null)
let $xeToolbar: VxeToolbarConstructor
const computeTableId = computed(() => {
const { id } = props
if (id) {
if (XEUtils.isFunction(id)) {
return `${id({ $table: $xeTable, $grid: $xeGrid }) || ''}`
}
return `${id}`
}
return ''
})
const computeValidOpts = computed(() => {
return Object.assign({}, getConfig().table.validConfig, props.validConfig)
})
/**
* @deprecated
*/
const computeSXOpts = computed(() => {
const virtualXOpts = computeVirtualXOpts.value
return virtualXOpts
})
const computeScrollXThreshold = computed(() => {
const virtualXOpts = computeVirtualXOpts.value
const { threshold } = virtualXOpts
if (threshold) {
return XEUtils.toNumber(threshold)
}
return 0
})
/**
* @deprecated
*/
const computeSYOpts = computed(() => {
const virtualYOpts = computeVirtualYOpts.value
return virtualYOpts
})
const computeVirtualXOpts = computed(() => {
return Object.assign({}, getConfig().table.scrollX, getConfig().table.virtualXConfig, props.scrollX, props.virtualXConfig) as VxeTablePropTypes.VirtualXConfig & { gt: number }
})
const computeVirtualYOpts = computed(() => {
return Object.assign({}, getConfig().table.scrollY, getConfig().table.virtualYConfig, props.scrollY, props.virtualYConfig) as VxeTablePropTypes.VirtualYConfig & { gt: number }
})
const computeScrollbarOpts = computed(() => {
return Object.assign({}, getConfig().table.scrollbarConfig, props.scrollbarConfig)
})
const computeScrollbarXToTop = computed(() => {
const scrollbarOpts = computeScrollbarOpts.value
return !!(scrollbarOpts.x && scrollbarOpts.x.position === 'top')
})
const computeScrollbarYToLeft = computed(() => {
const scrollbarOpts = computeScrollbarOpts.value
return !!(scrollbarOpts.y && scrollbarOpts.y.position === 'left')
})
const computeScrollYThreshold = computed(() => {
const virtualYOpts = computeVirtualYOpts.value
const { threshold } = virtualYOpts
if (threshold) {
return XEUtils.toNumber(threshold)
}
return 0
})
const computeRowHeightMaps = computed(() => {
return reactData.rowHeightStore
})
const computeDefaultRowHeight = computed(() => {
const vSize = computeSize.value
const rowHeightMaps = computeRowHeightMaps.value
return rowHeightMaps[vSize || 'default'] || 18
})
const computeColumnOpts = computed(() => {
return Object.assign({}, getConfig().table.columnConfig, props.columnConfig)
})
const computeCurrentColumnOpts = computed(() => {
return Object.assign({}, getConfig().table.currentColumnConfig, props.currentColumnConfig)
})
const computeCellOpts = computed(() => {
const cellOpts = Object.assign({}, getConfig().table.cellConfig, props.cellConfig)
if (cellOpts.height) {
cellOpts.height = XEUtils.toNumber(cellOpts.height)
}
return cellOpts
})
const computeHeaderCellOpts = computed(() => {
const headerCellOpts = Object.assign({}, getConfig().table.headerCellConfig, props.headerCellConfig)
if (headerCellOpts.height) {
headerCellOpts.height = XEUtils.toNumber(headerCellOpts.height)
}
return headerCellOpts
})
const computeFooterCellOpts = computed(() => {
const footerCellOpts = Object.assign({}, getConfig().table.footerCellConfig, props.footerCellConfig)
if (footerCellOpts.height) {
footerCellOpts.height = XEUtils.toNumber(footerCellOpts.height)
}
return footerCellOpts
})
const computeRowOpts = computed(() => {
return Object.assign({}, getConfig().table.rowConfig, props.rowConfig)
})
const computeCurrentRowOpts = computed(() => {
return Object.assign({}, getConfig().table.currentRowConfig, props.currentRowConfig)
})
const computeRowDragOpts = computed(() => {
return Object.assign({}, getConfig().table.rowDragConfig, props.rowDragConfig)
})
const computeColumnDragOpts = computed(() => {
return Object.assign({}, getConfig().table.columnDragConfig, props.columnDragConfig)
})
const computeResizeOpts = computed(() => {
return Object.assign({}, getConfig().table.resizeConfig, props.resizeConfig) as VxeTablePropTypes.ResizeOpts
})
const computeResizableOpts = computed(() => {
return Object.assign({}, getConfig().table.resizableConfig, props.resizableConfig) as VxeTablePropTypes.ResizableOpts
})
const computeSeqOpts = computed(() => {
return Object.assign({ startIndex: 0 }, getConfig().table.seqConfig, props.seqConfig) as VxeTablePropTypes.SeqOpts
})
const computeRadioOpts = computed(() => {
return Object.assign({}, getConfig().table.radioConfig, props.radioConfig) as VxeTablePropTypes.RadioOpts
})
const computeCheckboxOpts = computed(() => {
return Object.assign({}, getConfig().table.checkboxConfig, props.checkboxConfig) as VxeTablePropTypes.CheckboxOpts
})
const computeTooltipOpts = computed(() => {
return Object.assign({}, getConfig().tooltip, getConfig().table.tooltipConfig, props.tooltipConfig)
})
const computeTableTipConfig = computed(() => {
const { tooltipStore } = reactData
const tooltipOpts = computeTooltipOpts.value
return Object.assign({}, tooltipOpts, tooltipStore.currOpts)
})
const computeValidTipConfig = computed(() => {
const tooltipOpts = computeTooltipOpts.value
return Object.assign({}, tooltipOpts)
})
const computeEditOpts = computed(() => {
return Object.assign({}, getConfig().table.editConfig, props.editConfig) as VxeTablePropTypes.EditOpts
})
const computeSortOpts = computed(() => {
return Object.assign({ orders: ['asc', 'desc', null] }, getConfig().table.sortConfig, props.sortConfig) as VxeTablePropTypes.SortOpts
})
const computeFilterOpts = computed(() => {
return Object.assign({}, getConfig().table.filterConfig, props.filterConfig) as VxeTablePropTypes.FilterOpts
})
const computeMouseOpts = computed(() => {
return Object.assign({}, getConfig().table.mouseConfig, props.mouseConfig) as VxeTablePropTypes.MouseOpts
})
const computeAreaOpts = computed(() => {
return Object.assign({}, getConfig().table.areaConfig, props.areaConfig) as VxeTablePropTypes.AreaOpts
})
const computeKeyboardOpts = computed(() => {
return Object.assign({}, getConfig().table.keyboardConfig, props.keyboardConfig) as VxeTablePropTypes.KeyboardOpts
})
const computeClipOpts = computed(() => {
return Object.assign({}, getConfig().table.clipConfig, props.clipConfig)
})
const computeFNROpts = computed(() => {
return Object.assign({}, getConfig().table.fnrConfig, props.fnrConfig) as VxeTablePropTypes.FNROpts
})
const computeMenuOpts = computed(() => {
return Object.assign({}, getConfig().table.menuConfig, props.menuConfig) as VxeTablePropTypes.MenuOpts
})
const computeLeftFixedWidth = computed(() => {
const { columnStore } = reactData
const { leftList } = columnStore
let leftWidth = 0
for (let i = 0; i < leftList.length; i++) {
const column = leftList[i]
leftWidth += column.renderWidth
}
return leftWidth
})
const computeRightFixedWidth = computed(() => {
const { columnStore } = reactData
const { rightList } = columnStore
let leftWidth = 0
for (let i = 0; i < rightList.length; i++) {
const column = rightList[i]
leftWidth += column.renderWidth
}
return leftWidth
})
const computeHeaderMenu = computed(() => {
const menuOpts = computeMenuOpts.value
const headerOpts = menuOpts.header
return headerOpts && headerOpts.options ? headerOpts.options : []
})
const computeBodyMenu = computed(() => {
const menuOpts = computeMenuOpts.value
const bodyOpts = menuOpts.body
return bodyOpts && bodyOpts.options ? bodyOpts.options : []
})
const computeFooterMenu = computed(() => {
const menuOpts = computeMenuOpts.value
const footerOpts = menuOpts.footer
return footerOpts && footerOpts.options ? footerOpts.options : []
})
const computeIsMenu = computed(() => {
const menuOpts = computeMenuOpts.value
const headerMenu = computeHeaderMenu.value
const bodyMenu = computeBodyMenu.value
const footerMenu = computeFooterMenu.value
return !!(props.menuConfig && isEnableConf(menuOpts) && (headerMenu.length || bodyMenu.length || footerMenu.length))
})
const computeMenuList = computed(() => {
const { ctxMenuStore } = reactData
const rest: any[] = []
ctxMenuStore.list.forEach((list) => {
list.forEach((item) => {
rest.push(item)
})
})
return rest
})
const computeExportOpts = computed(() => {
return Object.assign({}, getConfig().table.exportConfig, props.exportConfig) as VxeTablePropTypes.ExportOpts
})
const computeImportOpts = computed(() => {
return Object.assign({}, getConfig().table.importConfig, props.importConfig) as VxeTablePropTypes.ImportOpts
})
const computePrintOpts = computed(() => {
return Object.assign({}, getConfig().table.printConfig, props.printConfig) as VxeTablePropTypes.PrintOpts
})
const computeExpandOpts = computed(() => {
return Object.assign({}, getConfig().table.expandConfig, props.expandConfig) as VxeTablePropTypes.ExpandOpts
})
const computeTreeOpts = computed(() => {
return Object.assign({}, getConfig().table.treeConfig, props.treeConfig) as VxeTablePropTypes.TreeOpts
})
const computeEmptyOpts = computed(() => {
return Object.assign({}, getConfig().table.emptyRender, props.emptyRender) as VxeTablePropTypes.EmptyOpts
})
const computeLoadingOpts = computed(() => {
return Object.assign({}, getConfig().table.loadingConfig, props.loadingConfig) as VxeTablePropTypes.LoadingOpts
})
const computeCellOffsetWidth = computed(() => {
return props.border ? Math.max(2, Math.ceil(reactData.scrollbarWidth / reactData.tableColumn.length)) : 1
})
const computeCustomOpts = computed(() => {
return Object.assign({}, getConfig().table.customConfig, props.customConfig)
})
const computeTableRowExpandedList = computed(() => {
const { treeConfig } = props
const { rowExpandedFlag, expandColumn } = reactData
const { visibleDataRowIdData, rowExpandedMaps } = internalData
const treeOpts = computeTreeOpts.value
const { transform } = treeOpts
const expandList: any[] = []
if (expandColumn && rowExpandedFlag) {
if (treeConfig && !transform) {
return XEUtils.values(rowExpandedMaps)
}
XEUtils.each(rowExpandedMaps, (row, rowid) => {
if (visibleDataRowIdData[rowid]) {
expandList.push(row)
}
})
}
return expandList
})
const computeAutoWidthColumnList = computed(() => {
const { visibleColumn } = internalData
const { tableColumn } = reactData
return tableColumn.length || visibleColumn.length ? visibleColumn.filter(column => column.width === 'auto' || column.minWidth === 'auto') : []
})
const computeFixedColumnSize = computed(() => {
const { tableColumn } = reactData
const { collectColumn } = internalData
let fixedSize = 0
// 只判断第一层
if (tableColumn.length && collectColumn.length) {
collectColumn.forEach((column) => {
if (column.renderFixed) {
fixedSize++
}
})
}
return fixedSize
})
const computeIsMaxFixedColumn = computed(() => {
const fixedColumnSize = computeFixedColumnSize.value
const columnOpts = computeColumnOpts.value
const { maxFixedSize } = columnOpts
if (maxFixedSize) {
return fixedColumnSize >= maxFixedSize
}
return false
})
const computeTableBorder = computed(() => {
const { border } = props
if (border === true) {
return 'full'
}
if (border) {
return border
}
return 'default'
})
const computeIsAllCheckboxDisabled = computed(() => {
const { treeConfig } = props
const { tableData } = reactData
const { tableFullData } = internalData
const checkboxOpts = computeCheckboxOpts.value
const { strict, checkMethod } = checkboxOpts
if (strict) {
if (tableData.length || tableFullData.length) {
if (checkMethod) {
if (treeConfig) {
// 暂时不支持树形结构
}
// 如果所有行都被禁用
return tableFullData.every((row) => !checkMethod({ row }))
}
return false
}
return true
}
return false
})
const computeVirtualScrollBars = computed(() => {
const { overflowX, scrollXLoad, overflowY, scrollYLoad } = reactData
return {
x: overflowX && scrollXLoad,
y: overflowY && scrollYLoad
}
})
const refMaps: VxeTablePrivateRef = {
refElem,
refTooltip,
refValidTooltip,
refTableFilter,
refTableCustom,
refTableMenu,
refTableHeader,
refTableBody,
refTableFooter,
refTableLeftHeader,
refTableLeftBody,
refTableLeftFooter,
refTableRightHeader,
refTableRightBody,
refTableRightFooter,
refLeftContainer,
refRightContainer,
refColResizeBar,
refRowResizeBar,
refScrollXVirtualElem,
refScrollYVirtualElem,
refScrollXHandleElem,
refScrollYHandleElem,
refScrollXSpaceElem,
refScrollYSpaceElem
}
const computeMaps: VxeTablePrivateComputed = {
computeSize,
computeTableId,
computeValidOpts,
computeVirtualXOpts,
computeVirtualYOpts,
computeScrollbarOpts,
computeScrollbarXToTop,
computeScrollbarYToLeft,
computeColumnOpts,
computeCurrentColumnOpts,
computeScrollXThreshold,
computeScrollYThreshold,
computeRowHeightMaps,
computeDefaultRowHeight,
computeCellOpts,
computeHeaderCellOpts,
computeFooterCellOpts,
computeRowOpts,
computeCurrentRowOpts,
computeRowDragOpts,
computeColumnDragOpts,
computeResizeOpts,
computeResizableOpts,
computeSeqOpts,
computeRadioOpts,
computeCheckboxOpts,
computeTooltipOpts,
computeEditOpts,
computeSortOpts,
computeFilterOpts,
computeMouseOpts,
computeAreaOpts,
computeKeyboardOpts,
computeClipOpts,
computeFNROpts,
computeHeaderMenu,
computeBodyMenu,
computeFooterMenu,
computeIsMenu,
computeMenuList,
computeMenuOpts,
computeExportOpts,
computeImportOpts,
computePrintOpts,
computeExpandOpts,
computeTreeOpts,
computeEmptyOpts,
computeLoadingOpts,
computeCellOffsetWidth,
computeCustomOpts,
computeLeftFixedWidth,
computeRightFixedWidth,
computeFixedColumnSize,
computeIsMaxFixedColumn,
computeIsAllCheckboxDisabled,
computeVirtualScrollBars,
computeSXOpts,
computeSYOpts
}
const $xeTable = {
xID,
props: props as VxeTableProps,
context,
reactData,
internalData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps,
xeGrid: $xeGrid,
xegrid: $xeGrid
} as unknown as VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods
const eqCellValue = (row1: any, row2: any, field: string) => {
const val1 = XEUtils.get(row1, field)
const val2 = XEUtils.get(row2, field)
if (eqEmptyValue(val1) && eqEmptyValue(val2)) {
return true
}
if (XEUtils.isString(val1) || XEUtils.isNumber(val1)) {
return ('' + val1) === ('' + val2)
}
return XEUtils.isEqual(val1, val2)
}
const getNextSortOrder = (column: VxeTableDefines.ColumnInfo) => {
const sortOpts = computeSortOpts.value
const { orders } = sortOpts
const currOrder = column.order || null
const oIndex = orders.indexOf(currOrder) + 1
return orders[oIndex < orders.length ? oIndex : 0]
}
const getCustomStorageMap = (id?: string) => {
const version = getConfig().version
const rest = XEUtils.toStringJSON(localStorage.getItem(customStorageKey) || '')
const maps = rest && rest._v === version ? rest : { _v: version }
return (id ? maps[id] : maps) || {}
}
const setCustomStorageMap = (id: string, data: any) => {
const version = getConfig().version
const maps = getCustomStorageMap()
maps[id] = data || undefined
maps._v = version
localStorage.setItem(customStorageKey, XEUtils.toJSONString(maps))
}
const getRecoverRowMaps = (keyMaps: Record<string, any>) => {
const { fullAllDataRowIdData } = internalData
const restKeys: Record<string, any> = {}
XEUtils.each(keyMaps, (row, rowid) => {
if (fullAllDataRowIdData[rowid]) {
restKeys[rowid] = row
}
})
return restKeys
}
const handleReserveRow = (reserveRowMap: any) => {
const { fullDataRowIdData } = internalData
const reserveList: any[] = []
XEUtils.each(reserveRowMap, (item, rowid) => {
if (fullDataRowIdData[rowid] && $xeTable.findRowIndexOf(reserveList, fullDataRowIdData[rowid].row) === -1) {
reserveList.push(fullDataRowIdData[rowid].row)
}
})
return reserveList
}
const handleVirtualXVisible = () => {
const { isScrollXBig, scrollXWidth } = reactData
const { elemStore, visibleColumn, fullColumnIdData } = internalData
const leftFixedWidth = computeLeftFixedWidth.value
const rightFixedWidth = computeRightFixedWidth.value
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (bodyScrollElem) {
const clientWidth = bodyScrollElem.clientWidth
let scrollLeft = bodyScrollElem.scrollLeft
if (isScrollXBig) {
scrollLeft = Math.ceil((scrollXWidth - clientWidth) * Math.min(1, (scrollLeft / (maxXWidth - clientWidth))))
}
const startLeft = scrollLeft + leftFixedWidth
const endLeft = scrollLeft + clientWidth - rightFixedWidth
let leftIndex = 0
let rightIndex = visibleColumn.length
while (leftIndex < rightIndex) {
const cIndex = Math.floor((leftIndex + rightIndex) / 2)
const column = visibleColumn[cIndex]
const colid = column.id
const colRest = fullColumnIdData[colid] || {}
if (colRest.oLeft <= startLeft) {
leftIndex = cIndex + 1
} else {
rightIndex = cIndex
}
}
let visibleSize = 0
const toVisibleIndex = Math.max(0, leftIndex < visibleColumn.length ? leftIndex - 2 : 0)
for (let cIndex = toVisibleIndex, cLen = visibleColumn.length; cIndex < cLen; cIndex++) {
const column = visibleColumn[cIndex]
const colid = column.id
const colRest = fullColumnIdData[colid] || {}
visibleSize++
if (colRest.oLeft > endLeft || visibleSize >= 60) {
break
}
}
return { toVisibleIndex: Math.max(0, toVisibleIndex), visibleSize: Math.max(1, visibleSize) }
}
return { toVisibleIndex: 0, visibleSize: 6 }
}
const calcVarRowHeightConfig = (sizeKey: 'default' | 'medium' | 'small' | 'mini', sizeEl: Element) => {
const { rowHeightStore } = reactData
if (sizeEl && sizeEl.clientHeight) {
rowHeightStore[sizeKey] = sizeEl.clientHeight
}
}
const computeRowHeight = () => {
const { isAllOverflow } = reactData
const tableHeader = refTableHeader.value
const tableBody = refTableBody.value
const tableBodyElem = tableBody ? tableBody.$el as HTMLDivElement : null
const defaultRowHeight = computeDefaultRowHeight.value
let rowHeight = 0
if (isAllOverflow) {
if (tableBodyElem) {
const tableHeaderElem = tableHeader ? tableHeader.$el as HTMLDivElement : null
let firstTrElem
firstTrElem = tableBodyElem.querySelector('tr')
if (!firstTrElem && tableHeaderElem) {
firstTrElem = tableHeaderElem.querySelector('tr')
}
if (firstTrElem) {
rowHeight = firstTrElem.clientHeight
}
}
if (!rowHeight) {
rowHeight = defaultRowHeight
}
} else {
rowHeight = defaultRowHeight
}
// 最低支持 18px 行高
return Math.max(18, rowHeight)
}
const handleVirtualYVisible = () => {
const { isAllOverflow, expandColumn, isScrollYBig, scrollYHeight } = reactData
const { elemStore, isResizeCellHeight, afterFullData, fullAllDataRowIdData } = internalData
const rowOpts = computeRowOpts.value
const cellOpts = computeCellOpts.value
const defaultRowHeight = computeDefaultRowHeight.value
const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
if (bodyScrollElem) {
const clientHeight = bodyScrollElem.clientHeight
let scrollTop = bodyScrollElem.scrollTop
if (isScrollYBig) {
scrollTop = Math.ceil((scrollYHeight - clientHeight) * Math.min(1, (scrollTop / (maxYHeight - clientHeight))))
}
const startTop = scrollTop
const endTop = scrollTop + clientHeight
let toVisibleIndex = -1
let visibleSize = 0
const isCustomCellHeight = isResizeCellHeight || cellOpts.height || rowOpts.height
if (!isCustomCellHeight && !expandColumn && isAllOverflow) {
toVisibleIndex = Math.floor(startTop / defaultRowHeight) - 1
visibleSize = Math.ceil(clientHeight / defaultRowHeight) + 1
} else {
const { handleGetRowId } = createHandleGetRowId($xeTable)
let leftIndex = 0
let rightIndex = afterFullData.length
while (leftIndex < rightIndex) {
const rIndex = Math.floor((leftIndex + rightIndex) / 2)
const row = afterFullData[rIndex]
const rowid = handleGetRowId(row)
const rowRest = fullAllDataRowIdData[rowid] || {}
if (rowRest.oTop <= startTop) {
leftIndex = rIndex + 1
} else {
rightIndex = rIndex
}
}
toVisibleIndex = Math.max(0, leftIndex < afterFullData.length ? leftIndex - 2 : 0)
for (let rIndex = toVisibleIndex, rLen = afterFullData.length; rIndex < rLen; rIndex++) {
const row = afterFullData[rIndex]
const rowid = handleGetRowId(row)
const rowRest = fullAllDataRowIdData[rowid] || {}
visibleSize++
if (rowRest.oTop > endTop || visibleSize >= 100) {
break
}
}
}
return { toVisibleIndex: Math.max(0, toVisibleIndex), visibleSize: Math.max(6, visibleSize) }
}
return { toVisibleIndex: 0, visibleSize: 6 }
}
const calculateMergerOffsetIndex = (list: any[], offsetItem: any, type: 'row' | 'col') => {
for (let mcIndex = 0, len = list.length; mcIndex < len; mcIndex++) {
const mergeItem = list[mcIndex]
const { startIndex, endIndex } = offsetItem
const mergeStartIndex = mergeItem[type]
const mergeSpanNumber = mergeItem[type + 'span']
const mergeEndIndex = mergeStartIndex + mergeSpanNumber
if (mergeStartIndex < startIndex && startIndex < mergeEndIndex) {
offsetItem.startIndex = mergeStartIndex
}
if (mergeStartIndex < endIndex && endIndex < mergeEndIndex) {
offsetItem.endIndex = mergeEndIndex
}
if (offsetItem.startIndex !== startIndex || offsetItem.endIndex !== endIndex) {
mcIndex = -1
}
}
}
function buildMergeData (mergeConfigs: VxeTableDefines.MergeItem[]) {
const mergeMaps: Record<string, VxeTableDefines.MergeCacheItem> = {}
if (mergeConfigs && mergeConfigs.length) {
for (let mIndex = 0; mIndex < mergeConfigs.length; mIndex++) {
const { row: _rowIndex, col: _columnIndex, rowspan: mergeRowspan, colspan: mergeColspan } = mergeConfigs[mIndex]
for (let i = 0; i < mergeRowspan; i++) {
for (let j = 0; j < mergeColspan; j++) {
mergeMaps[`${_rowIndex + i}:${_columnIndex + j}`] = !i && !j
? {
rowspan: mergeRowspan,
colspan: mergeColspan
}
: {
rowspan: 0,
colspan: 0
}
}
}
}
}
return mergeMaps
}
const handleBodyMerge = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
const { fullAllDataRowIdData, fullColumnIdData, visibleColumn, afterFullData, mergeBodyList, mergeBodyMaps } = internalData
if (merges) {
const { handleGetRowId } = createHandleGetRowId($xeTable)
if (!XEUtils.isArray(merges)) {
merges = [merges]
}
merges.forEach((item) => {
let { row: margeRow, col: margeCol, rowspan, colspan } = item
let mergeRowIndex = -1
let mergeColumnIndex = -1
if (XEUtils.isNumber(margeRow)) {
mergeRowIndex = margeRow
} else {
const rowid = margeRow ? handleGetRowId(margeRow) : null
const rowRest = rowid ? fullAllDataRowIdData[rowid] : null
if (rowRest) {
mergeRowIndex = rowRest._index
}
}
if (XEUtils.isNumber(margeCol)) {
mergeColumnIndex = margeCol
} else {
const colid = margeCol ? margeCol.id : null
const colRest = colid ? fullColumnIdData[colid] : null
if (colRest) {
mergeColumnIndex = colRest._index
}
}
if (mergeRowIndex > -1 && mergeColumnIndex > -1 && (rowspan || colspan)) {
rowspan = XEUtils.toNumber(rowspan) || 1
colspan = XEUtils.toNumber(colspan) || 1
if (rowspan > 1 || colspan > 1) {
const row = afterFullData[mergeRowIndex]
const column = visibleColumn[mergeColumnIndex]
let mergeItem = mergeBodyMaps[`${mergeRowIndex}:${mergeColumnIndex}`]
if (mergeItem) {
mergeItem.rowspan = rowspan
mergeItem.colspan = colspan
mergeItem._rowspan = rowspan
mergeItem._colspan = colspan
} else {
mergeItem = {
row: mergeRowIndex,
col: mergeColumnIndex,
rowspan,
colspan,
_row: row,
_col: column,
_rowspan: rowspan,
_colspan: colspan
}
mergeBodyMaps[`${mergeRowIndex}:${mergeColumnIndex}`] = mergeItem
mergeBodyList.push(mergeItem)
}
}
}
})
}
}
const handleFooterMerge = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
const { footerTableData } = reactData
const { mergeFooterList, mergeFooterMaps } = internalData
if (merges) {
const { visibleColumn } = internalData
if (!XEUtils.isArray(merges)) {
merges = [merges]
}
merges.forEach((item) => {
let { row: margeRow, col: margeCol, rowspan, colspan } = item
const mergeRowIndex = XEUtils.isNumber(margeRow) ? margeRow : -1
let mergeColumnIndex = -1
if (XEUtils.isNumber(margeCol)) {
mergeColumnIndex = margeCol
}
if (mergeRowIndex > -1 && mergeColumnIndex > -1 && (rowspan || colspan)) {
rowspan = XEUtils.toNumber(rowspan) || 1
colspan = XEUtils.toNumber(colspan) || 1
if (rowspan > 1 || colspan > 1) {
const row = footerTableData[mergeRowIndex]
const column = visibleColumn[mergeColumnIndex]
let mergeItem = mergeFooterMaps[`${mergeRowIndex}:${mergeColumnIndex}`]
if (mergeItem) {
mergeItem.rowspan = rowspan
mergeItem.colspan = colspan
mergeItem._rowspan = rowspan
mergeItem._colspan = colspan
} else {
mergeItem = {
row: mergeRowIndex,
col: mergeColumnIndex,
rowspan,
colspan,
_row: row,
_col: column,
_rowspan: rowspan,
_colspan: colspan
}
mergeFooterMaps[`${mergeRowIndex}:${mergeColumnIndex}`] = mergeItem
mergeFooterList.push(mergeItem)
}
}
}
})
}
}
const removeMerges = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[], mList: VxeTableDefines.MergeItem[], rowList?: any) => {
const rest: VxeTableDefines.MergeItem[] = []
if (merges) {
// const { treeConfig } = props
const { visibleColumn } = internalData
if (!XEUtils.isArray(merges)) {
merges = [merges]
}
// if (treeConfig && merges.length) {
// errLog('vxe.error.noTree', ['merge-cells | merge-footer-items'])
// }
merges.forEach((item) => {
let { row, col } = item
if (rowList && XEUtils.isNumber(row)) {
row = rowList[row]
}
if (XEUtils.isNumber(col)) {
col = visibleColumn[col]
}
const mcIndex = XEUtils.findIndexOf(mList, item => (item._row === row || getRowid($xeTable, item._row) === getRowid($xeTable, row)) && ((item as any)._col.id === col || item._col.id === (col as VxeTableDefines.ColumnInfo).id))
if (mcIndex > -1) {
const rItems = mList.splice(mcIndex, 1)
rest.push(rItems[0])
}
})
}
return rest
}
const clearAllSort = () => {
const { tableFullColumn } = internalData
tableFullColumn.forEach((column) => {
column.order = null
})
}
const calcTableHeight = (key: 'height' | 'minHeight' | 'maxHeight') => {
const { parentHeight } = reactData
const val = props[key]
let num = 0
if (val) {
if (val === '100%' || val === 'auto') {
num = parentHeight
} else {
const excludeHeight = $xeTable.getExcludeHeight()
if (isScale(val)) {
num = Math.floor((XEUtils.toInteger(val) || 1) / 100 * parentHeight)
} else {
num = XEUtils.toNumber(val)
}
num = Math.max(40, num - excludeHeight)
}
}
return num
}
const handleCustomRestore = (storeData: VxeTableDefines.CustomStoreData) => {
let { collectColumn } = internalData
const { resizableData, sortData, visibleData, fixedData } = storeData
let hasCustomSort = false
// 处理还原
if (resizableData || sortData || visibleData || fixedData) {
XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
const colKey = column.getKey()
// 支持一级
if (!parentColumn) {
if (fixedData && fixedData[colKey] !== undefined) {
column.fixed = fixedData[colKey]
}
if (sortData && XEUtils.isNumber(sortData[colKey])) {
hasCustomSort = true
column.renderSortNumber = sortData[colKey]
}
}
if (resizableData && XEUtils.isNumber(resizableData[colKey])) {
column.resizeWidth = resizableData[colKey]
}
if (visibleData && XEUtils.isBoolean(visibleData[colKey])) {
column.visible = visibleData[colKey]
}
})
// 如果自定义了顺序
if (hasCustomSort) {
collectColumn = XEUtils.orderBy(collectColumn, 'renderSortNumber')
internalData.collectColumn = collectColumn
internalData.tableFullColumn = getColumnList(collectColumn)
}
reactData.isCustomStatus = true
} else {
reactData.isCustomStatus = false
}
}
/**
* 还原自定义列操作状态
*/
const restoreCustomStorage = () => {
const { customConfig } = props
const tableId = computeTableId.value
const customOpts = computeCustomOpts.value
const { storage, restoreStore } = customOpts
const isAllCustom = storage === true
const storageOpts: VxeTableDefines.VxeTableCustomStorageObj = isAllCustom ? {} : Object.assign({}, storage || {})
const isCustomResizable = isAllCustom || storageOpts.resizable
const isCustomVisible = isAllCustom || storageOpts.visible
const isCustomFixed = isAllCustom || storageOpts.fixed
const isCustomSort = isAllCustom || storageOpts.sort
if ((customConfig ? isEnableConf(customOpts) : customOpts.enabled) && (isCustomResizable || isCustomVisible || isCustomFixed || isCustomSort)) {
if (!tableId) {
errLog('vxe.error.reqProp', ['id'])
return
}
const storeData: VxeTableDefines.CustomStoreData = getCustomStorageMap(tableId)
if (restoreStore) {
return Promise.resolve(
restoreStore({ id: tableId, type: 'restore', storeData })
).then(storeData => {
if (!storeData) {
return
}
return handleCustomRestore(storeData)
}).catch(e => e)
} else {
return handleCustomRestore(storeData)
}
}
}
/**
* 更新数据列的 Map
* 牺牲数据组装的耗时,用来换取使用过程中的流畅
*/
const cacheColumnMap = () => {
const { tableFullColumn, collectColumn } = internalData
const fullColumnIdData: Record<string, VxeTableDefines.ColumnCacheItem> = internalData.fullColumnIdData = {}
const fullColumnFieldData: Record<string, VxeTableDefines.ColumnCacheItem> = internalData.fullColumnFieldData = {}
const mouseOpts = computeMouseOpts.value
const expandOpts = computeExpandOpts.value
const columnOpts = computeColumnOpts.value
const columnDragOpts = computeColumnDragOpts.value
const virtualYOpts = computeVirtualYOpts.value
const { isCrossDrag, isSelfToChildDrag } = columnDragOpts
const customOpts = computeCustomOpts.value
const { storage } = customOpts
const rowOpts = computeRowOpts.value
const isGroup = collectColumn.some(hasChildrenList)
let isAllOverflow = !!props.showOverflow
let expandColumn: VxeTableDefines.ColumnInfo | undefined
let treeNodeColumn: VxeTableDefines.ColumnInfo | undefined
let checkboxColumn: VxeTableDefines.ColumnInfo | undefined
let radioColumn: VxeTableDefines.ColumnInfo | undefined
let htmlColumn: VxeTableDefines.Colu