UNPKG

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.

1,390 lines (1,265 loc) 489 kB
import { h, ComponentPublicInstance, reactive, ref, Ref, provide, inject, nextTick, onActivated, onDeactivated, onBeforeUnmount, onUnmounted, watch, computed, onMounted } from 'vue' import { defineVxeComponent } from '../../ui/src/comp' import XEUtils from 'xe-utils' import { initTpImg, getTpImg, isPx, isScale, hasClass, addClass, removeClass, getEventTargetNode, getPaddingTopBottomSize, setScrollTop, setScrollLeft, toCssUnit, hasControlKey } from '../../ui/src/dom' import { getLastZIndex, nextZIndex, hasChildrenList, getFuncText, isEnableConf, formatText, eqEmptyValue } from '../../ui/src/utils' import { VxeUI } from '../../ui' import { createInternalData, getRowUniqueId, clearTableAllStatus, getColumnList, toFilters, hasDeepKey, getRowkey, getRowid, rowToVisible, colToVisible, getCellValue, setCellValue, handleRowidOrRow, handleFieldOrColumn, toTreePathSeq, restoreScrollLocation, getRootColumn, getRefElem, getColReMinWidth, createHandleUpdateRowId, createHandleGetRowId, getCalcHeight, getCellRestHeight } from './util' import { getSlotVNs } from '../../ui/src/vn' import { moveRowAnimateToTb, clearRowAnimate, moveColAnimateToLr, clearColAnimate } from './anime' import { warnLog, errLog } from '../../ui/src/log' 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 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 '../module/filter/hook' import '../module/menu/hook' import '../module/edit/hook' import '../module/export/hook' import '../module/keyboard/hook' import '../module/validator/hook' import '../module/custom/hook' import '../render' import type { VxeTooltipInstance, VxeTabsConstructor, VxeTabsPrivateMethods, ValueOf, VxeComponentSlotType } from 'vxe-pc-ui' import type { VxeGridConstructor, VxeGridPrivateMethods, VxeTableConstructor, TableReactData, VxeTablePropTypes, VxeToolbarConstructor, TablePrivateMethods, VxeTablePrivateRef, VxeTablePrivateComputed, VxeTablePrivateMethods, TableMethods, VxeTableMethods, VxeTableDefines, VxeTableEmits, VxeTableProps, VxeColumnPropTypes, VxeTableCustomPanelConstructor } 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 defineVxeComponent({ 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('VxeLoading') const VxeUITooltipComponent = VxeUI.getComponent('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: [], // 行分组列信息 rowGroupColumn: null, // 展开列信息 expandColumn: null, checkboxColumn: null, radioColumn: 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 }, visiblwRowsFlag: 1, isRowGroupStatus: false, rowGroupList: [], aggHandleFields: [], aggHandleAggColumns: [], rowGroupExpandedFlag: 1, 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, lazScrollLoading: 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 = createInternalData() 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<VxeTableCustomPanelConstructor> 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) const $xeGantt = inject('$xeGantt', 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 computeRowField = computed(() => { const rowOpts = computeRowOpts.value return `${props.rowId || rowOpts.keyField || '_X_ROW_KEY'}` }) 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(() => { const { virtualXConfig, scrollX } = props const globalVirtualXConfig = getConfig().table.virtualXConfig const globalScrollX = getConfig().table.scrollX if (virtualXConfig) { return Object.assign({}, globalVirtualXConfig, virtualXConfig) as VxeTablePropTypes.VirtualXConfig & { gt: number } } if (scrollX) { // 已废弃,保留兼容 return Object.assign({}, globalScrollX, scrollX) as VxeTablePropTypes.VirtualXConfig & { gt: number } } if (globalVirtualXConfig) { return Object.assign({}, globalVirtualXConfig, virtualXConfig) as VxeTablePropTypes.VirtualXConfig & { gt: number } } // 已废弃,保留兼容 return Object.assign({}, globalScrollX, scrollX) as VxeTablePropTypes.VirtualXConfig & { gt: number } }) const computeVirtualYOpts = computed(() => { const { virtualYConfig, scrollY } = props const globalVirtualYConfig = getConfig().table.virtualYConfig const globalScrollY = getConfig().table.scrollY if (virtualYConfig) { return Object.assign({}, globalVirtualYConfig, virtualYConfig) as VxeTablePropTypes.VirtualYConfig & { gt: number } } if (scrollY) { // 已废弃,保留兼容 return Object.assign({}, globalScrollY, scrollY) as VxeTablePropTypes.VirtualYConfig & { gt: number } } if (globalVirtualYConfig) { return Object.assign({}, globalVirtualYConfig, virtualYConfig) as VxeTablePropTypes.VirtualYConfig & { gt: number } } // 已废弃,保留兼容 return Object.assign({}, globalScrollY, scrollY) 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) const cellOpts = computeCellOpts.value headerCellOpts.height = XEUtils.toNumber(getCalcHeight(headerCellOpts.height || cellOpts.height)) return headerCellOpts }) const computeFooterCellOpts = computed(() => { const footerCellOpts = Object.assign({}, getConfig().table.footerCellConfig, props.footerCellConfig) const cellOpts = computeCellOpts.value footerCellOpts.height = XEUtils.toNumber(getCalcHeight(footerCellOpts.height || cellOpts.height)) return footerCellOpts }) const computeRowOpts = computed(() => { return Object.assign({}, getConfig().table.rowConfig, props.rowConfig) }) const computeAggregateOpts = computed(() => { return Object.assign({}, getConfig().table.aggregateConfig || getConfig().table.rowGroupConfig, props.aggregateConfig || props.rowGroupConfig) }) const computeRowGroupOpts = computed(() => { return computeAggregateOpts.value }) 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(() => { const fnrOpts = computeFnrOpts.value return fnrOpts }) 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 { tableData, rowExpandedFlag, expandColumn, rowGroupExpandedFlag, treeExpandedFlag } = reactData const { visibleDataRowIdData, rowExpandedMaps } = internalData const expandList: any[] = [] if (tableData.length && expandColumn && rowExpandedFlag && rowGroupExpandedFlag && treeExpandedFlag) { 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({ $table: $xeTable, row })) } return false } return true } return false }) const computeVirtualScrollBars = computed(() => { const { overflowX, scrollXLoad, overflowY, scrollYLoad } = reactData return { x: overflowX && scrollXLoad, y: overflowY && scrollYLoad } }) const computeRowGroupFields = computed(() => { const rowGroupOpts = computeRowGroupOpts.value return rowGroupOpts.groupFields }) const computeRowGroupColumns = computed(() => { const { rowGroupList } = reactData const { fullColumnFieldData } = internalData const rgColumns: VxeTableDefines.ColumnInfo[] = [] rowGroupList.forEach(aggConf => { const colRest = fullColumnFieldData[aggConf.field] if (colRest) { rgColumns.push(colRest.column) } }) return rgColumns }) 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, computeRowField, computeVirtualXOpts, computeVirtualYOpts, computeScrollbarOpts, computeScrollbarXToTop, computeScrollbarYToLeft, computeColumnOpts, computeCurrentColumnOpts, computeScrollXThreshold, computeScrollYThreshold, computeRowHeightMaps, computeDefaultRowHeight, computeCellOpts, computeHeaderCellOpts, computeFooterCellOpts, computeRowOpts, computeAggregateOpts, computeRowGroupOpts, 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, computeRowGroupFields, computeRowGroupColumns, computeFNROpts, computeSXOpts, computeSYOpts } const $xeTable = { xID, props: props as VxeTableProps, context, reactData, internalData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps, xeGrid: $xeGrid, xeGantt: $xeGantt, // 已废弃 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 handleKeyField = () => { const keyField = computeRowField.value internalData.currKeyField = keyField internalData.isCurrDeepKey = hasDeepKey(keyField) } const hangleStorageDefaultValue = (value: boolean | null | undefined, isAll: boolean) => { return XEUtils.isBoolean(value) ? value : isAll } 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 = leftIndex === visibleColumn.length ? leftIndex : 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 = leftIndex === afterFullData.length ? leftIndex : 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 handleUpdateMergeBodyCells = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => { internalData.mergeBodyList = [] internalData.mergeBodyMaps = {} internalData.mergeBodyCellMaps = {} $xeTable.setMergeCells(merges) } 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 handleUpdateMergeFooterCells = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => { internalData.mergeFooterList = [] internalData.mergeFooterMaps = {} internalData.mergeFooterCellMaps = {} $xeTable.setMergeFooterItems(merges) } const handleFooterMerge = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => { const { footerTableData } = reactData const { mergeFooterList, mergeFooterMaps, fullColumnIdData } = 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 } 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 = 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 removeBodyMerges = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => { const { mergeBodyList, fullColumnIdData, fullAllDataRowIdData, mergeBodyMaps } = internalData const rest: VxeTableDefines.MergeItem[] = [] if (merges) { const { handleGetRowId } = createHandleGetRowId($xeTable) if (!XEUtils.isArray(merges)) { merges = [merges] } merges.forEach((item) => { const { row: margeRow, col: margeCol } = 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 } } const mcIndex = XEUtils.findIndexOf(mergeBodyList, item => item.row === mergeRowIndex && item.col === mergeColumnIndex) if (mcIndex > -1) { const rItems = mergeBodyList.splice(mcIndex, 1) const item = rItems[0] if (item) { rest.push(rItems[0]) if (mergeBodyMaps[`${mergeRowIndex}:${mergeColumnIndex}`]) { delete mergeBodyMaps[`${mergeRowIndex}:${mergeColumnIndex}`] } } } }) } return rest } const removeFooterMerges = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => { const { mergeFooterList, fullColumnIdData, mergeFooterMaps } = internalData const rest: VxeTableDefines.MergeItem[] = [] if (merges) { if (!XEUtils.isArray(merges)) { merges = [merges] } merges.forEach((item) => { const { row: margeRow, col: margeCol } = item const mergeRowIndex = XEUtils.isNumber(margeRow) ? margeRow : -1 let mergeColumnIndex = -1 if (XEUtils.isNumber(margeCol)) { mergeColumnIndex = margeCol } else { const colid = margeCol ? margeCol.id : null const colRest = colid ? fullColumnIdData[colid] : null if (colRest) { mergeColumnIndex = colRest._index } } const mcIndex = XEUtils.findIndexOf(mergeFooterList, item => item.row === mergeRowIndex && item.col === mergeColumnIndex) if (mcIndex > -1) { const rItems = mergeFooterList.splice(mcIndex, 1) const item = rItems[0] if (item) { rest.push(item) if (mergeFooterMaps[`${mergeRowIndex}:${mergeColumnIndex}`]) { delete mergeFooterMaps[`${mergeRowIndex}:${mergeColumnIndex}`] } } } }) } return rest } const handleSortEvent = (evnt: Event | null, sortConfs: VxeTableDefines.SortConfs | VxeTableDefines.SortConfs[], isUpdate?: boolean) => { const { tableFullColumn } = internalData const sortOpts = computeSortOpts.value const { multiple, remote, orders } = sortOpts if (!XEUtils.isArray(sortConfs)) { sortConfs = [sortConfs] } if (sortConfs && sortConfs.length) { const orderActiveMaps: Record<string, VxeTableDefines.ColumnInfo> = {} if (!multiple) { sortConfs = [sortConfs[0]] tableFullColumn.forEach((column) => { if (column.order) { orderActiveMaps[column.id] = column } }) } const sortColMpps: Record<string, VxeTableDefines.ColumnInfo> = {} let firstColumn: an