import { h, ComponentPublicInstance, reactive, ref, Ref, provide, inject, nextTick, Teleport, 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, wheelScrollTopTo, wheelScrollLeftTo, getEventTargetNode, getPaddingTopBottomSize, setScrollTop, setScrollLeft, toCssUnit, hasControlKey, checkTargetElement } from '../../ui/src/dom'
import { getLastZIndex, nextZIndex, hasChildrenList, getFuncText, isEnableConf, formatText, eqEmptyValue } from '../../ui/src/utils'
import { VxeUI } from '../../ui'
import { createReactData, createInternalData, getRowUniqueId, createRowId, clearTableAllStatus, getColumnList, toFilters, hasDeepKey, getRowkey, getRowid, rowToVisible, colToVisible, getCellValue, setCellValue, handleRowidOrRow, handleFieldOrColumn, toTreePathSeq, restoreScrollLocation, getRootColumn, getRefElem, getColReMinWidth, createHandleUpdateRowId, createHandleGetRowId, getCalcHeight, getCellRestHeight, getLastChildColumn } from './util'
import { getSlotVNs } from '../../ui/src/vn'
import { moveRowAnimateToTb, clearRowAnimate, moveColAnimateToLr, clearColAnimate } from '../../ui/src/anime'
import { warnLog, errLog } from '../../ui/src/log'
import { getCrossTableDragRowInfo } from './store'
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, VxeComponentStyleType } from 'vxe-pc-ui'
import type { VxeGridConstructor, VxeGridPrivateMethods, VxeTableConstructor, 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

const sourceType = 'table'

let crossTableDragRowObj: {
  $oldTable: VxeTableConstructor & VxeTablePrivateMethods
  $newTable: (VxeTableConstructor & VxeTablePrivateMethods) | null
} | null = null

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 $xeParentTable = inject<(VxeTableConstructor & VxeTablePrivateMethods) | null>('$xeTable', null)
    const $xeGrid = inject<(VxeGridConstructor & VxeGridPrivateMethods) | null>('$xeGrid', null)
    const $xeGantt = inject<VxeTableDefines.InjectGanttType | null>('$xeGantt', null)
    const $xeGGWrapper = $xeGrid || $xeGantt

    const { computeSize } = useFns.useSize(props)

    const crossTableDragRowInfo = getCrossTableDragRowInfo()

    const reactData = reactive(createReactData())

    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 refTeleportWrapper = ref<HTMLDivElement>()
    const refPopupWrapperElem = ref<HTMLDivElement>()
    const refCustomContainerElem = ref<HTMLDivElement>()

    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>()

    let $xeToolbar: VxeToolbarConstructor

    const computeTableId = computed(() => {
      const { id } = props
      if (id) {
        if (XEUtils.isFunction(id)) {
          return `${id({ $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }) || ''}`
        }
        return `${id}`
      }
      return ''
    })

    const computeRowField = computed(() => {
      const rowOpts = computeRowOpts.value
      return `${props.rowId || rowOpts.keyField || '_X_ROW_KEY'}`
    })

    const computeValidOpts = computed(() => {
      const opts = Object.assign({}, getConfig().table.validConfig, props.validConfig)
      // 兼容老版本
      if (XEUtils.isBoolean(opts.showMessage)) {
        opts.showErrorMessage = opts.showMessage
      }
      return opts
    })

    /**
     * @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 computeScrollbarXOpts = computed(() => {
      const scrollbarOpts = computeScrollbarOpts.value
      return Object.assign({}, scrollbarOpts.x, props.scrollbarConfig?.x || {})
    })

    const computeScrollbarYOpts = computed(() => {
      const scrollbarOpts = computeScrollbarOpts.value
      return Object.assign({}, scrollbarOpts.y, props.scrollbarConfig?.y || {})
    })

    const computeScrollbarXToTop = computed(() => {
      const scrollbarXOpts = computeScrollbarXOpts.value
      return scrollbarXOpts.position === 'top'
    })

    const computeScrollbarYToLeft = computed(() => {
      const scrollbarYOpts = computeScrollbarYOpts.value
      return scrollbarYOpts.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 defaultRowHeight = computeDefaultRowHeight.value
      const cellOpts = computeCellOpts.value
      let headCellHeight = XEUtils.toNumber(getCalcHeight(headerCellOpts.height || cellOpts.height))
      if ($xeGantt) {
        const { computeTaskViewScales } = $xeGantt.getComputeMaps()
        const taskViewScales = computeTaskViewScales.value
        if (taskViewScales && taskViewScales.length > 2) {
          const ganttMinHeadCellHeight = defaultRowHeight / 2 * taskViewScales.length
          headCellHeight = Math.max(ganttMinHeadCellHeight, headCellHeight)
        }
      }
      headerCellOpts.height = headCellHeight
      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 computeAggregateAccuracyOpts = computed(() => {
      return Object.assign({}, getConfig().table.aggregateAccuracyConfig, props.aggregateAccuracyConfig)
    })

    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 computeHeaderTooltipOpts = computed(() => {
      return Object.assign({}, getConfig().tooltip, getConfig().table.headerTooltipConfig, props.headerTooltipConfig)
    })

    const computeFooterTooltipOpts = computed(() => {
      return Object.assign({}, getConfig().tooltip, getConfig().table.footerTooltipConfig, props.footerTooltipConfig)
    })

    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 computeEditDirtyOpts = computed(() => {
      return Object.assign({}, getConfig().table.editDirtyConfig, props.editDirtyConfig)
    })

    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 computeFloatingFilterOpts = computed(() => {
      return Object.assign({}, getConfig().table.floatingFilterConfig, props.floatingFilterConfig)
    })

    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 computeUndoHistoryOpts = computed(() => {
      return Object.assign({}, getConfig().table.undoHistoryConfig, props.undoHistoryConfig)
    })

    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 computeBodyMergeCoverFixed = computed(() => {
      const { columnStore, mergeBodyFlag } = reactData
      const { mergeBodyList, visibleColumn } = internalData
      const { leftList, rightList } = columnStore
      const rscIndex = visibleColumn.length - rightList.length
      if (mergeBodyFlag && (leftList.length || rightList.length)) {
        const lecIndex = leftList.length
        for (let i = 0; i < mergeBodyList.length; i++) {
          const { col, colspan } = mergeBodyList[i]
          if (col < lecIndex && col + colspan > lecIndex) {
            return true
          } else if (col < rscIndex && col + colspan > rscIndex) {
            return true
          }
        }
      }
      return false
    })

    const computeIsHeaderRenderOptimize = computed(() => {
      const { spanMethod, footerSpanMethod, showHeaderOverflow: allColumnHeaderOverflow } = props
      const { isGroup, scrollXLoad } = reactData
      let isOptimizeMode = false
      if (isGroup) {
        // 分组表头
      } else {
        // 如果是使用优化模式
        if (scrollXLoad && allColumnHeaderOverflow) {
          if (spanMethod || footerSpanMethod) {
            // 如果不支持优化模式
          } else {
            isOptimizeMode = true
          }
        }
      }
      return isOptimizeMode
    })

    const computeIsBodyRenderOptimize = computed(() => {
      const { spanMethod, footerSpanMethod } = props
      const { scrollXLoad, scrollYLoad, isAllOverflow, expandColumn } = reactData
      const bodyMergeCoverFixed = computeBodyMergeCoverFixed.value
      const expandOpts = computeExpandOpts.value
      let isOptimizeMode = false
      // 如果是使用优化模式
      if (scrollXLoad || scrollYLoad || isAllOverflow) {
        // 如果是展开行，内联模式，不支持优化
        // 如果是方法合并，不支持优化
        // 如果固定列且配置式合并，不支持优化
        if ((expandColumn && expandOpts.mode !== 'fixed') || bodyMergeCoverFixed || spanMethod || footerSpanMethod) {
          // 如果不支持优化模式
        } else {
          isOptimizeMode = true
        }
      }
      return isOptimizeMode
    })

    const computeIsFooterRenderOptimize = computed(() => {
      const { spanMethod, footerSpanMethod, showFooterOverflow: allColumnFooterOverflow } = props
      const { scrollXLoad } = reactData
      let isOptimizeMode = false
      // 如果是使用优化模式
      if (scrollXLoad && allColumnFooterOverflow) {
        if (spanMethod || footerSpanMethod) {
          // 如果不支持优化模式
        } else {
          isOptimizeMode = true
        }
      }
      return isOptimizeMode
    })

    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 isContentMenu = computeIsContentMenu.value
      return isContentMenu
    })

    const computeIsContentMenu = 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 computeCustomSimpleMode = computed(() => {
      const { minHeight, height } = props
      const customOpts = computeCustomOpts.value
      const { mode, popupOptions, placement } = customOpts
      if (!placement || placement === 'top-left' || placement === 'top-right') {
        if (!(mode === 'modal' || mode === 'drawer')) {
          const { mode } = popupOptions || {}
          if (!mode || mode === 'auto') {
            if (height || minHeight) {
              return 'inside'
            }
            return 'outside'
          }
          if (mode) {
            return mode
          }
        }
      }
      return ''
    })

    const computeTableStyle = computed(() => {
      const scrollbarOpts = computeScrollbarOpts.value
      const { width, height } = scrollbarOpts
      const tStys: VxeComponentStyleType = {}
      if (width) {
        tStys['--vxe-ui-table-view-scrollbar-width'] = toCssUnit(width)
      }
      if (height) {
        tStys['--vxe-ui-table-view-scrollbar-height'] = toCssUnit(height)
      }
      return tStys
    })

    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 computeKeepFields = computed(() => {
      const { tableFullColumn } = internalData
      const { updateColFlag } = reactData
      const editDirtyOpts = computeEditDirtyOpts.value
      const { includeFields, excludeFields } = editDirtyOpts
      const kpFields: string[] = []
      if (updateColFlag) {
        if (includeFields && includeFields.length) {
          return includeFields
        }
        const exfMaps: Record<string, number> = {}
        if (excludeFields && excludeFields.length) {
          excludeFields.forEach(field => {
            exfMaps[field] = 1
          })
        }
        for (let i = 0; i < tableFullColumn.length; i++) {
          const column = tableFullColumn[i]
          const { field, type, editRender, cellRender } = column
          if (field && !type && (editRender || cellRender) && !exfMaps[field]) {
            kpFields.push(field)
          }
        }
      }
      return kpFields
    })

    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 computeAggFuncColumns = computed(() => {
      const { rowGroupList, tableColumn } = reactData
      if (rowGroupList.length) {
        return tableColumn.filter(column => column.aggFunc)
      }
      return []
    })

    const refMaps: VxeTablePrivateRef = {
      refElem,
      refTooltip,
      refValidTooltip,
      refTableFilter,
      refTableCustom,
      refTableMenu,
      refTableHeader,
      refTableBody,
      refTableFooter,
      refTableLeftHeader,
      refTableLeftBody,
      refTableLeftFooter,
      refTableRightHeader,
      refTableRightBody,
      refTableRightFooter,
      refLeftContainer,
      refRightContainer,
      refColResizeBar,
      refRowResizeBar,
      refCustomContainerElem,
      refScrollXVirtualElem,
      refScrollYVirtualElem,
      refScrollXHandleElem,
      refScrollYHandleElem,
      refScrollXSpaceElem,
      refScrollYSpaceElem
    }

    const computeMaps: VxeTablePrivateComputed = {
      computeSize,
      computeTableId,
      computeValidOpts,
      computeRowField,
      computeVirtualXOpts,
      computeVirtualYOpts,
      computeScrollbarOpts,
      computeScrollbarXOpts,
      computeScrollbarYOpts,
      computeScrollbarXToTop,
      computeScrollbarYToLeft,
      computeColumnOpts,
      computeCurrentColumnOpts,
      computeScrollXThreshold,
      computeScrollYThreshold,
      computeRowHeightMaps,
      computeDefaultRowHeight,
      computeCellOpts,
      computeHeaderCellOpts,
      computeFooterCellOpts,
      computeRowOpts,
      computeAggregateOpts,
      computeAggregateAccuracyOpts,
      computeRowGroupOpts,
      computeCurrentRowOpts,
      computeRowDragOpts,
      computeColumnDragOpts,
      computeResizeOpts,
      computeResizableOpts,
      computeSeqOpts,
      computeRadioOpts,
      computeCheckboxOpts,
      computeTooltipOpts,
      computeHeaderTooltipOpts,
      computeFooterTooltipOpts,
      computeEditOpts,
      computeEditDirtyOpts,
      computeSortOpts,
      computeFilterOpts,
      computeFloatingFilterOpts,
      computeMouseOpts,
      computeAreaOpts,
      computeKeyboardOpts,
      computeClipOpts,
      computeFnrOpts,
      computeHeaderMenu,
      computeBodyMenu,
      computeFooterMenu,
      computeIsMenu,
      computeIsContentMenu,
      computeMenuList,
      computeMenuOpts,
      computeExportOpts,
      computeImportOpts,
      computePrintOpts,
      computeExpandOpts,
      computeTreeOpts,
      computeEmptyOpts,
      computeLoadingOpts,
      computeCellOffsetWidth,
      computeCustomOpts,
      computeCustomSimpleMode,
      computeLeftFixedWidth,
      computeRightFixedWidth,
      computeBodyMergeCoverFixed,
      computeFixedColumnSize,
      computeIsMaxFixedColumn,
      computeKeepFields,
      computeIsAllCheckboxDisabled,
      computeIsHeaderRenderOptimize,
      computeIsBodyRenderOptimize,
      computeIsFooterRenderOptimize,
      computeVirtualScrollBars,
      computeRowGroupFields,
      computeRowGroupColumns,
      computeAggFuncColumns,
      computeUndoHistoryOpts,

      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[], mergeMaps: Record<string, VxeTableDefines.MergeCacheRow | VxeTableDefines.MergeCacheCol>, offsetItem: VxeTableDefines.MergeCacheRow | VxeTableDefines.MergeCacheCol, type: 'row' | 'col') => {
      const mKey = `${offsetItem.startIndex}:${offsetItem.endIndex}`
      const mObj = mergeMaps[mKey]
      // 缓存
      if (mObj) {
        offsetItem.startIndex = mObj.startIndex
        offsetItem.endIndex = mObj.endIndex
      } else {
        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
          }
        }
        mergeMaps[mKey] = offsetItem
      }
    }

    function buildMergeData (mergeConfigs: VxeTableDefines.MergeItem[]) {
      const mergeMaps: Record<string, VxeTableDefines.MergeCacheItem> = {}
      const mergeRowMaps: Record<string, VxeTableDefines.MergeCacheRow> = {}
      const mergeColMaps: Record<string, VxeTableDefines.MergeCacheCol> = {}
      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++) {
            const currRIndex = _rowIndex + i
            for (let j = 0; j < mergeColspan; j++) {
              const currCIndex = _columnIndex + j
              mergeMaps[`${currRIndex}:${currCIndex}`] = !i && !j
                ? {
                    rowspan: mergeRowspan,
                    colspan: mergeColspan
                  }
                : {
                    rowspan: 0,
                    colspan: 0
                  }
            }
          }
        }
      }
      return { mergeMaps, mergeRowMaps, mergeColMaps }
    }

    const handleUpdateMergeBodyCells = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
      internalData.mergeBodyList = []
      internalData.mergeBodyMaps = {}
      internalData.mergeBodyCellMaps = {}
      internalData.mergeBodyRowMaps = {}
      internalData.mergeBodyColMaps = {}
      $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 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 handleUpdateMergeHeaderCells = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
      internalData.mergeHeaderList = []
      internalData.mergeHeaderMaps = {}
      internalData.mergeHeaderCellMaps = {}
      internalData.mergeHeaderRowMaps = {}
      internalData.mergeBodyColMaps = {}
      $xeTable.setMergeHeaderCells(merges)
    }

    const handleHeaderMerge = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
      const { showCustomHeader } = props
      const { footerTableData } = reactData
      const { mergeHeaderList, mergeHeaderMaps, 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 (!showCustomHeader && rowspan > 1) {
              errLog('vxe.error.notSupportProp', ['[table] show-custom-header=false', `rowspan=${rowspan}`, 'rowspan=1'])
              return
            }
            if (rowspan > 1 || colspan > 1) {
              const row = footerTableData[mergeRowIndex]
              const column = visibleColumn[mergeColumnIndex]
              let mergeItem = mergeHeaderMaps[`${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
                }
                mergeHeaderMaps[`${mergeRowIndex}:${mergeColumnIndex}`] = mergeItem
                mergeHeaderList.push(mergeItem)
              }
            }
          }
        })
      }
    }

    const removeHeaderMerges = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
      const { mergeHeaderList, fullColumnIdData, mergeHeaderMaps } = 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(mergeHeaderList, item => item.row === mergeRowIndex && item.col === mergeColumnIndex)
          if (mcIndex > -1) {
            const rItems = mergeHeaderList.splice(mcIndex, 1)
            const item = rItems[0]
            if (item) {
              rest.push(item)
              if (mergeHeaderMaps[`${mergeRowIndex}:${mergeColumnIndex}`]) {
                delete mergeHeaderMaps[`${mergeRowIndex}:${mergeColumnIndex}`]
              }
            }
          }
        })
      }
      return rest
    }

    const handleUpdateMergeFooterCells = (merges: VxeTableDefines.MergeOptions | VxeTableDefines.MergeOptions[]) => {
      internalData.mergeFooterList = []
      internalData.mergeFooterMaps = {}
      internalData.mergeFooterCellMaps = {}
      internalData.mergeFooterRowMaps = {}
      internalData.mergeFooterColMaps = {}
      $xeTable.setMergeFooterCells(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 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: any = null
        sortConfs.forEach((confs: any, index: number) => {
          let { field, order } = confs
          let column = field
          if (XEUtils.isString(field)) {
            column = $xeTable.getColumnByField(field)
          }
          if (!firstColumn) {
            firstColumn = column
          }
          if (column && column.sortable) {
            if (orders && orders.indexOf(order) === -1) {
              order = getNextSortOrder(column)
            }
            if (column.order !== order) {
              column.order = order
            }
            column.sortTime = Date.now() + index
            sortColMpps[column.id] = column
          }
        })
        if (!multiple) {
          XEUtils.each(orderActiveMaps, (oaCol: VxeTableDefines.ColumnInfo, oaId) => {
            if (!sortColMpps[oaId]) {
              oaCol.order = null
            }
          })
        }
        if (isUpdate) {
          if (!remote) {
            $xeTable.handleTableData(true)
          }
        }
        if (evnt) {
          $xeTable.handleColumnSortEvent(evnt, firstColumn)
        }
        return nextTick().then(() => {
          updateRowOffsetTop()
          $xeTable.updateCellAreas()
          return updateStyle()
        })
      }
      return nextTick()
    }

    const clearAllSort = () => {
      const { tableFullColumn } = internalData
      tableFullColumn.forEach((column) => {
        column.order = null
      })
    }

    const calcTableHeight = (key: 'height' | 'minHeight' | 'maxHeight') => {
      const { editConfig, editRules } = props
      const { parentHeight, tableColumn } = reactData
      let val = props[key]
      if (key === 'minHeight') {
        const defMinHeight = getConfig().table.minHeight
        if (XEUtils.eqNull(val)) {
          if (eqEmptyValue(defMinHeight)) {
            if (!tableColumn.length) {
              // 如果全部列被隐藏
              val = 40
            } else if (editRules && isEnableConf(editConfig)) {
              // 编辑模式默认最小高度
              val = 144
            }
          } else {
            val = defMinHeight
          }
        }
      }
      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) => {
      const { aggregateConfig, rowGroupConfig } = props
      const { collectColumn } = internalData
      const customOpts = computeCustomOpts.value
      const { storage, storeOptions } = customOpts
      const isAllCustom = storage === true
      const storageOpts: VxeTableDefines.VxeTableCustomStorageObj = Object.assign({}, isAllCustom ? {} : storage || {}, storeOptions)
      const isCustomResizable = hangleStorageDefaultValue(storageOpts.resizable, isAllCustom)
      const isCustomVisible = hangleStorageDefaultValue(storageOpts.visible, isAllCustom)
      const isCustomFixed = hangleStorageDefaultValue(storageOpts.fixed, isAllCustom)
      const isCustomSort = hangleStorageDefaultValue(storageOpts.sort, isAllCustom)
      const isCustomAggGroup = hangleStorageDefaultValue(storageOpts.aggGroup, isAllCustom)
      const isCustomAggFunc = hangleStorageDefaultValue(storageOpts.aggFunc, isAllCustom)
      let { resizableData, sortData, visibleData, fixedData, aggGroupData, aggFuncData } = storeData
      // 处理还原
      if ((isCustomResizable && resizableData) || (isCustomSort && sortData) || (isCustomVisible && visibleData) || (isCustomFixed && fixedData) || (isCustomAggGroup && aggGroupData) || (isCustomAggFunc && aggFuncData)) {
        const sortColMaps: Record<string, {
          key: string
          sNum: number
          pKey: string | null
        }> = {}
        if (isCustomSort && sortData) {
          // 转换兼容老版本数据，即将废弃兼容
          if (!XEUtils.isArray(sortData)) {
            const sortRests: {key: string, index: number}[] = []
            XEUtils.each(sortData, (index: number, colKey: string) => {
              sortRests.push({ key: colKey, index })
            })
            sortData = XEUtils.orderBy(sortRests, { field: 'index', order: 'asc' }).map(item => ({ k: item.key }))
          }
          let colNum = 1
          XEUtils.eachTree(sortData, (sObj, index, sOjs, path, pSObj) => {
            sortColMaps[sObj.k] = {
              key: sObj.k,
              sNum: colNum++,
              pKey: pSObj ? pSObj.k : null
            }
          }, { children: 'c' })
        }
        const colKeyMaps: Record<string, VxeTableDefines.ColumnInfo> = {}
        const allCols: VxeTableDefines.ColumnInfo[] = []
        const aggGroupConfs: VxeTableDefines.RowGroupItem[] = []
        XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
          const colKey = column.getKey()
          // 支持一级
          if (!parentColumn) {
            if (isCustomFixed && fixedData && fixedData[colKey] !== undefined) {
              column.fixed = fixedData[colKey]
            }
          }
          if (isCustomResizable && resizableData && XEUtils.isNumber(resizableData[colKey])) {
            column.resizeWidth = resizableData[colKey]
          }
          if (isCustomVisible && visibleData && XEUtils.isBoolean(visibleData[colKey])) {
            column.visible = visibleData[colKey]
          }
          if (isCustomAggFunc && aggFuncData && (aggregateConfig || rowGroupConfig) && aggFuncData[colKey]) {
            column.aggFunc = aggFuncData[colKey]
          }
          if (isCustomAggGroup && aggGroupData && aggGroupData[colKey]) {
            aggGroupConfs.push({ field: column.field })
          }
          colKeyMaps[colKey] = column
          allCols.push(column)
        })
        if ((aggregateConfig || rowGroupConfig) && aggGroupConfs.length) {
          const groupRest = handleGroupData(internalData.tableFullData, aggGroupConfs)
          internalData.tableFullTreeData = []
          internalData.tableFullGroupData = groupRest.treeData
          reactData.isRowGroupStatus = true
          reactData.rowGroupList = aggGroupConfs
          $xeTable.cacheRowMap(false)
        }
        // 如果自定义了顺序
        if (isCustomSort && sortData) {
          allCols.forEach(column => {
            const colKey = column.getKey()
            const scItem = sortColMaps[colKey]
            if (scItem) {
              const parentColumn = scItem.pKey ? colKeyMaps[scItem.pKey] : null
              column.parentId = parentColumn ? parentColumn.id : null
              column.renderSortNumber = scItem.sNum
            }
          })
          const newCollectCols = XEUtils.toArrayTree(XEUtils.orderBy(allCols, 'renderSortNumber'), { key: 'id', parentKey: 'parentId', children: 'children' })
          internalData.collectColumn = newCollectCols
          internalData.tableFullColumn = getColumnList(newCollectCols)
          reactData.updateColFlag++
        }
        reactData.isCustomStatus = true
      } else {
        reactData.isCustomStatus = false
      }
    }

    /**
     * 还原自定义列操作状态
     */
    const restoreCustomStorage = () => {
      const { customConfig } = props
      const tableId = computeTableId.value
      const customOpts = computeCustomOpts.value
      const { storage, restoreStore, storeOptions } = customOpts
      const isAllCustom = storage === true
      const storageOpts: VxeTableDefines.VxeTableCustomStorageObj = Object.assign({}, isAllCustom ? {} : storage || {}, storeOptions)
      const isCustomResizable = hangleStorageDefaultValue(storageOpts.resizable, isAllCustom)
      const isCustomVisible = hangleStorageDefaultValue(storageOpts.visible, isAllCustom)
      const isCustomFixed = hangleStorageDefaultValue(storageOpts.fixed, isAllCustom)
      const isCustomSort = hangleStorageDefaultValue(storageOpts.sort, isAllCustom)
      const isCustomAggGroup = hangleStorageDefaultValue(storageOpts.aggGroup, isAllCustom)
      const isCustomAggFunc = hangleStorageDefaultValue(storageOpts.aggFunc, isAllCustom)
      if (storage && (customConfig ? isEnableConf(customOpts) : customOpts.enabled) && (isCustomResizable || isCustomVisible || isCustomFixed || isCustomSort || isCustomAggGroup || isCustomAggFunc)) {
        if (!tableId) {
          errLog('vxe.error.reqProp', ['id'])
          return
        }
        const storeData: VxeTableDefines.CustomStoreData = getCustomStorageMap(tableId)
        if (restoreStore) {
          return Promise.resolve(
            restoreStore({ $table: $xeTable, id: tableId, type: 'restore', storeData })
          ).then(storeData => {
            if (!storeData) {
              return
            }
            return handleCustomRestore(storeData)
          }).catch(e => e)
        } else {
          return handleCustomRestore(storeData)
        }
      }
    }

    const updateColumnAllOverflow = () => {
      const { showOverflow } = props
      const { isGroup } = reactData
      const { fullAllDataRowIdData, tableFullColumn, collectColumn } = internalData
      let isAllOverflow = !!showOverflow
      const handleFunc = (column: VxeTableDefines.ColumnInfo) => {
        if (isAllOverflow && column.showOverflow === false) {
          isAllOverflow = false
        }
      }
      if (isGroup) {
        XEUtils.eachTree(collectColumn, handleFunc)
      } else {
        tableFullColumn.forEach(handleFunc)
      }
      XEUtils.each(fullAllDataRowIdData, (rowRest: VxeTableDefines.RowCacheItem) => {
        rowRest.height = 0
      })
      reactData.isAllOverflow = isAllOverflow
    }

    /**
     * 更新数据列的 Map
     * 牺牲数据组装的耗时，用来换取使用过程中的流畅
     */
    const cacheColumnMap = () => {
      const { treeConfig, showOverflow } = props
      const { tableFullColumn, collectColumn } = internalData
      const fullColIdData: Record<string, VxeTableDefines.ColumnCacheItem> = internalData.fullColumnIdData = {}
      const fullColFieldData: 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 treeOpts = computeTreeOpts.value
      const { storage } = customOpts
      const rowOpts = computeRowOpts.value
      const isGroup = collectColumn.some(hasChildrenList)
      let isAllOverflow = !!showOverflow
      let rowGroupColumn: VxeTableDefines.ColumnInfo | undefined
      let expandColumn: VxeTableDefines.ColumnInfo | undefined
      let treeNodeColumn: VxeTableDefines.ColumnInfo | undefined
      let checkboxColumn: VxeTableDefines.ColumnInfo | undefined
      let radioColumn: VxeTableDefines.ColumnInfo | undefined
      let htmlColumn: VxeTableDefines.ColumnInfo | undefined
      let hasFixed: VxeColumnPropTypes.Fixed | undefined
      const handleFunc = (column: VxeTableDefines.ColumnInfo, index: number, items: VxeTableDefines.ColumnInfo[], path?: string[], parentColumn?: VxeTableDefines.ColumnInfo) => {
        const { id: colid, field, fixed, type, treeNode, rowGroupNode } = column
        const rest = { $index: -1, _index: -1, column, colid, index, items, parent: parentColumn || null, width: 0, oLeft: 0 }
        if (field) {
          if (fullColFieldData[field]) {
            errLog('vxe.error.colRepet', ['field', field])
          }
          fullColFieldData[field] = rest
        } else {
          if (storage && !type) {
            errLog('vxe.error.reqSupportProp', ['storage', `[${type ? `type=${type}` : `title=${column.getTitle()}`}]field=?`])
          }
          if (columnOpts.drag && (isCrossDrag || isSelfToChildDrag)) {
            errLog('vxe.error.reqSupportProp', ['column-drag-config.isCrossDrag | column-drag-config.isSelfToChildDrag', `${column.getTitle() || type || ''} -> field=?`])
          }
        }
        if (!hasFixed && fixed) {
          hasFixed = fixed
        }
        if (!htmlColumn && type === 'html') {
          htmlColumn = column
        }
        if (treeNode) {
          if (treeNodeColumn) {
            warnLog('vxe.error.colRepet', ['tree-node', treeNode])
          }
          if (!treeNodeColumn) {
            treeNodeColumn = column
          }
        }
        if (rowGroupNode) {
          if (treeNodeColumn) {
            warnLog('vxe.error.colRepet', ['row-group-node', rowGroupNode])
          }
          if (!rowGroupColumn) {
            rowGroupColumn = column
          }
        }
        if (type === 'expand') {
          if (expandColumn) {
            warnLog('vxe.error.colRepet', ['type', type])
          }
          if (!expandColumn) {
            expandColumn = column
          }
        }
        if (type === 'checkbox') {
          if (checkboxColumn) {
            warnLog('vxe.error.colRepet', ['type', type])
          }
          if (!checkboxColumn) {
            checkboxColumn = column
          }
        } else if (type === 'radio') {
          if (radioColumn) {
            warnLog('vxe.error.colRepet', ['type', type])
          }
          if (!radioColumn) {
            radioColumn = column
          }
        }
        if (isAllOverflow && column.showOverflow === false) {
          isAllOverflow = false
        }
        if (fullColIdData[colid]) {
          errLog('vxe.error.colRepet', ['colId', colid])
        }
        fullColIdData[colid] = rest
      }
      if (isGroup) {
        XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn, nodes) => {
          column.level = nodes.length
          handleFunc(column, index, items, path, parentColumn)
        })
      } else {
        tableFullColumn.forEach(handleFunc)
      }

      if (expandColumn) {
        if (expandOpts.mode !== 'fixed' && virtualYOpts.enabled) {
          warnLog('vxe.error.notConflictProp', ['column.type="expand', 'virtual-y-config.enabled=false'])
        }
        if ((expandOpts.mode !== 'fixed') && mouseOpts.area) {
          errLog('vxe.error.errConflicts', ['mouse-config.area', 'column.type=expand'])
        }
        if (expandOpts.mode !== 'inside' && (treeConfig && !treeOpts.transform)) {
          errLog('vxe.error.notConflictProp', ['tree-config.transform=false', 'expand-config.mode=fixed'])
        }
        if (props.spanMethod) {
          warnLog('vxe.error.notSupportProp', ['column.type=expand', 'span-method', 'span-method=null'])
        }
      }

      if (htmlColumn) {
        if (!columnOpts.useKey) {
          errLog('vxe.error.notSupportProp', ['column.type=html', 'column-config.useKey=false', 'column-config.useKey=true'])
        }
        if (!rowOpts.useKey) {
          errLog('vxe.error.notSupportProp', ['column.type=html', 'row-config.useKey=false', 'row-config.useKey=true'])
        }
      }

      reactData.isGroup = isGroup
      reactData.rowGroupColumn = rowGroupColumn
      reactData.treeNodeColumn = treeNodeColumn
      reactData.expandColumn = expandColumn
      reactData.checkboxColumn = checkboxColumn
      reactData.radioColumn = radioColumn
      reactData.isAllOverflow = isAllOverflow
    }

    const updateHeight = () => {
      internalData.customHeight = calcTableHeight('height')
      internalData.customMinHeight = calcTableHeight('minHeight')
      internalData.customMaxHeight = calcTableHeight('maxHeight')

      // 如果启用虚拟滚动，纠正高度
      if (reactData.scrollYLoad && !(internalData.customHeight || internalData.customMinHeight || internalData.customMaxHeight)) {
        internalData.customHeight = 300
      }
    }

    /**
     * 计算自适应列宽
     */
    const calcColumnAutoWidth = (column: VxeTableDefines.ColumnInfo, wrapperEl: HTMLDivElement) => {
      const columnOpts = computeColumnOpts.value
      const { autoOptions } = columnOpts
      const { isCalcHeader, isCalcBody, isCalcFooter } = autoOptions || {}
      const querySelections: string[] = []
      if (isCalcHeader) {
        querySelections.push(`.vxe-header-cell--wrapper[colid="${column.id}"]`)
      }
      if (isCalcBody) {
        querySelections.push(`.vxe-body-cell--wrapper[colid="${column.id}"]`)
      }
      if (isCalcFooter) {
        querySelections.push(`.vxe-footer-cell--wrapper[colid="${column.id}"]`)
      }
      const cellElemList = querySelections.length ? wrapperEl.querySelectorAll(querySelections.join(',')) : []
      let leftRightPadding = 0
      const firstCellEl = cellElemList[0]
      if (firstCellEl && firstCellEl.parentElement) {
        const cellStyle = getComputedStyle(firstCellEl.parentElement)
        leftRightPadding = Math.ceil(XEUtils.toNumber(cellStyle.paddingLeft) + XEUtils.toNumber(cellStyle.paddingRight))
      }
      let colWidth = column.renderAutoWidth - leftRightPadding
      for (let i = 0; i < cellElemList.length; i++) {
        const celEl = cellElemList[i] as HTMLDivElement
        colWidth = Math.max(colWidth, celEl ? Math.ceil(celEl.scrollWidth) + 4 : 0)
      }
      return colWidth + leftRightPadding
    }

    /**
     * 自适应列宽
     */
    const calcCellWidth = () => {
      const autoWidthColumnList = computeAutoWidthColumnList.value
      const { fullColumnIdData } = internalData
      const el = refElem.value
      if (el) {
        el.setAttribute('data-calc-col', 'Y')
        autoWidthColumnList.forEach(column => {
          const colid = column.id
          const colRest = fullColumnIdData[colid]
          const colWidth = calcColumnAutoWidth(column, el)
          if (colRest) {
            colRest.width = Math.max(colWidth, colRest.width)
          }
          column.renderAutoWidth = colWidth
        })
        $xeTable.analyColumnWidth()
        el.removeAttribute('data-calc-col')
      }
    }

    /**
     * 列宽算法，计算单元格列宽，动态分配可用剩余空间
     * 支持 px、%、固定 混合分配
     * 支持动态列表调整分配
     * 支持自动分配偏移量
     * 支持 width=60 width=60px width=10% min-width=60 min-width=60px min-width=10%
     */
    const autoCellWidth = () => {
      const { elemStore } = internalData
      const bodyWrapperElem = getRefElem(elemStore['main-body-wrapper'])
      if (!bodyWrapperElem) {
        return
      }
      const yHandleEl = refScrollYHandleElem.value
      if (!yHandleEl) {
        return
      }
      const xHandleEl = refScrollXHandleElem.value
      if (!xHandleEl) {
        return
      }
      let tWidth = 0
      const minCellWidth = 40 // 列宽最少限制 40px
      const bodyWidth = bodyWrapperElem.clientWidth
      let remainWidth = bodyWidth
      let meanWidth = remainWidth / 100
      const { fit } = props
      const { columnStore } = reactData
      const { resizeList, pxMinList, autoMinList, pxList, scaleList, scaleMinList, autoList, remainList } = columnStore
      // 最小宽
      pxMinList.forEach((column) => {
        const minWidth = XEUtils.toInteger(column.minWidth)
        tWidth += minWidth
        column.renderWidth = minWidth
      })
      // 最小自适应
      autoMinList.forEach((column) => {
        const caWidth = Math.max(60, XEUtils.toInteger(column.renderAutoWidth))
        tWidth += caWidth
        column.renderWidth = caWidth
      })
      // 最小百分比
      scaleMinList.forEach((column) => {
        const smWidth = Math.floor(XEUtils.toInteger(column.minWidth) * meanWidth)
        tWidth += smWidth
        column.renderWidth = smWidth
      })
      // 固定百分比
      scaleList.forEach((column) => {
        const sfWidth = Math.floor(XEUtils.toInteger(column.width) * meanWidth)
        tWidth += sfWidth
        column.renderWidth = sfWidth
      })
      // 固定宽
      pxList.forEach((column) => {
        const pWidth = XEUtils.toInteger(column.width)
        tWidth += pWidth
        column.renderWidth = pWidth
      })
      // 自适应宽
      autoList.forEach((column) => {
        const aWidth = Math.max(60, XEUtils.toInteger(column.renderAutoWidth))
        tWidth += aWidth
        column.renderWidth = aWidth
      })
      // 调整了列宽
      resizeList.forEach((column) => {
        const reWidth = XEUtils.toInteger(column.resizeWidth)
        tWidth += reWidth
        column.renderWidth = reWidth
      })
      remainWidth -= tWidth
      meanWidth = remainWidth > 0 ? Math.floor(remainWidth / (scaleMinList.length + pxMinList.length + autoMinList.length + remainList.length)) : 0
      if (fit) {
        if (remainWidth > 0) {
          scaleMinList.concat(pxMinList).concat(autoMinList).forEach((column) => {
            tWidth += meanWidth
            column.renderWidth += meanWidth
          })
        }
      } else {
        meanWidth = minCellWidth
      }
      // 剩余均分
      remainList.forEach((column) => {
        const width = Math.max(meanWidth, minCellWidth)
        column.renderWidth = width
        tWidth += width
      })
      if (fit) {
        /**
         * 偏移量算法
         * 如果所有列足够放的情况下，从最后动态列开始分配
         */
        const dynamicList = scaleList.concat(scaleMinList).concat(pxMinList).concat(autoMinList).concat(remainList)
        let dynamicSize = dynamicList.length - 1
        if (dynamicSize > 0) {
          let i = bodyWidth - tWidth
          if (i > 0) {
            while (i > 0 && dynamicSize >= 0) {
              i--
              dynamicList[dynamicSize--].renderWidth++
            }
            tWidth = bodyWidth
          }
        }
      }
      reactData.scrollXWidth = tWidth
      reactData.resizeWidthFlag++
      updateColumnOffsetLeft()
      updateHeight()
    }

    /**
     * 计算自适应行高
     */
    const calcCellAutoHeight = (rowRest: VxeTableDefines.RowCacheItem, wrapperEl: HTMLDivElement) => {
      const { scrollXLoad } = reactData
      const wrapperElemList = wrapperEl.querySelectorAll(`.vxe-cell--wrapper[rowid="${rowRest.rowid}"]`)
      let colHeight = 0
      let firstCellStyle: CSSStyleDeclaration | null = null
      let topBottomPadding = 0
      for (let i = 0; i < wrapperElemList.length; i++) {
        const wrapperElem = wrapperElemList[i] as HTMLElement
        const cellElem = wrapperElem.parentElement as HTMLTableCellElement
        const cellStyle = cellElem.style
        const orHeight = cellStyle.height
        if (!scrollXLoad) {
          cellStyle.height = ''
        }
        if (!firstCellStyle) {
          firstCellStyle = getComputedStyle(cellElem)
          topBottomPadding = firstCellStyle ? Math.ceil(XEUtils.toNumber(firstCellStyle.paddingTop) + XEUtils.toNumber(firstCellStyle.paddingBottom)) : 0
        }
        if (!scrollXLoad) {
          cellStyle.height = orHeight
        }
        const cellHeight = wrapperElem ? wrapperElem.clientHeight : 0
        colHeight = Math.max(colHeight, Math.ceil(cellHeight + topBottomPadding))
      }
      if (scrollXLoad) {
        colHeight = Math.max(colHeight, rowRest.height)
      }
      return colHeight
    }

    /**
     * 自适应行高
     */
    const calcCellHeight = () => {
      const { treeConfig } = props
      const { tableData, isAllOverflow, scrollYLoad, scrollXLoad } = reactData
      const { fullAllDataRowIdData } = internalData
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      const treeOpts = computeTreeOpts.value
      const defaultRowHeight = computeDefaultRowHeight.value
      if (!isAllOverflow && (scrollYLoad || scrollXLoad || (treeConfig && treeOpts.showLine))) {
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        el.setAttribute('data-calc-row', 'Y')
        tableData.forEach(row => {
          const rowid = handleGetRowId(row)
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            const reHeight = calcCellAutoHeight(rowRest, el)
            rowRest.height = Math.max(defaultRowHeight, reHeight)
          }
          el.removeAttribute('data-calc-row')
        })
        reactData.calcCellHeightFlag++
      }
    }

    const getOrderField = (column: VxeTableDefines.ColumnInfo) => {
      const { isRowGroupStatus } = reactData
      const { sortBy, sortType, aggFunc } = column
      return isRowGroupStatus && aggFunc
        ? (row: any) => {
            if (row.isAggregate) {
              const aggData = row.aggData
              const currAggData = aggData ? aggData[column.field] : null
              return currAggData ? currAggData.value : null
            }
            let cellValue
            if (sortBy) {
              cellValue = XEUtils.isFunction(sortBy) ? sortBy({ row, column }) : XEUtils.get(row, sortBy)
            } else {
              cellValue = $xeTable.getCellLabel(row, column)
            }
            if (!sortType || sortType === 'auto') {
              return isNaN(cellValue) ? cellValue : XEUtils.toNumber(cellValue)
            } else if (sortType === 'number') {
              return XEUtils.toNumber(cellValue)
            } else if (sortType === 'string') {
              return XEUtils.toValueString(cellValue)
            }
            return cellValue
          }
        : (row: any) => {
            let cellValue
            if (sortBy) {
              cellValue = XEUtils.isFunction(sortBy) ? sortBy({ row, column }) : XEUtils.get(row, sortBy)
            } else {
              cellValue = $xeTable.getCellLabel(row, column)
            }
            if (!sortType || sortType === 'auto') {
              return isNaN(cellValue) ? cellValue : XEUtils.toNumber(cellValue)
            } else if (sortType === 'number') {
              return XEUtils.toNumber(cellValue)
            } else if (sortType === 'string') {
              return XEUtils.toValueString(cellValue)
            }
            return cellValue
          }
    }

    const updateAfterListIndex = () => {
      const { treeConfig } = props
      const { afterFullData, fullDataRowIdData, fullAllDataRowIdData } = internalData
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      const fullMaps: Record<string, any> = {}
      afterFullData.forEach((row, index) => {
        const rowid = handleGetRowId(row)
        const rowRest = fullAllDataRowIdData[rowid]
        const seq = index + 1
        if (rowRest) {
          if (!treeConfig) {
            rowRest.seq = seq
          }
          rowRest._index = index
        } else {
          const rest = { row, rowid, seq, index: -1, $index: -1, _index: index, treeIndex: -1, _tIndex: -1, items: [], parent: null, level: 0, height: 0, resizeHeight: 0, oTop: 0, expandHeight: 0 }
          fullAllDataRowIdData[rowid] = rest
          fullDataRowIdData[rowid] = rest
        }
        fullMaps[rowid] = row
      })
      internalData.afterFullRowMaps = fullMaps
    }

    /**
     * 预编译
     * 对渲染中的数据提前解析序号及索引。牺牲提前编译耗时换取渲染中额外损耗，使运行时更加流畅
     */
    const updateAfterDataIndex = () => {
      const { treeConfig } = props
      const { fullDataRowIdData, fullAllDataRowIdData, afterFullData, afterTreeFullData } = internalData
      const treeOpts = computeTreeOpts.value
      const { transform } = treeOpts
      const childrenField = treeOpts.children || treeOpts.childrenField
      const fullMaps: Record<string, any> = {}
      if (treeConfig) {
        let _treeIndex = 0
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        XEUtils.eachTree(afterTreeFullData, (row, index, items, path) => {
          const rowid = handleGetRowId(row)
          const rowRest = fullAllDataRowIdData[rowid]
          const seq = path.map((num, i) => i % 2 === 0 ? (Number(num) + 1) : '.').join('')
          if (rowRest) {
            rowRest.seq = seq
            rowRest.treeIndex = index
            rowRest._tIndex = _treeIndex
          } else {
            const rest = { row, rowid, seq, index: -1, $index: -1, _index: -1, treeIndex: -1, _tIndex: _treeIndex, items: [], parent: null, level: 0, height: 0, resizeHeight: 0, oTop: 0, expandHeight: 0 }
            fullAllDataRowIdData[rowid] = rest
            fullDataRowIdData[rowid] = rest
          }
          _treeIndex++
          fullMaps[rowid] = row
        }, { children: transform ? treeOpts.mapChildrenField : childrenField })
        if (transform) {
          afterFullData.forEach((row, index) => {
            const rowid = handleGetRowId(row)
            const rowRest = fullAllDataRowIdData[rowid]
            const seq = index + 1
            if (rowRest) {
              if (!treeConfig) {
                rowRest.seq = seq
              }
              rowRest._index = index
            }
          })
        }
        internalData.afterFullRowMaps = fullMaps
      } else {
        updateAfterListIndex()
      }
    }

    /**
     * 如果为虚拟树、行分组、则将树结构拍平
     * @returns
     */
    const handleVirtualTreeToList = () => {
      const { treeConfig } = props
      const { isRowGroupStatus } = reactData
      const { fullAllDataRowIdData, treeExpandedMaps, rowGroupExpandedMaps } = internalData
      const aggregateOpts = computeAggregateOpts.value
      const treeOpts = computeTreeOpts.value
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      const fullData: any[] = []
      const expandMaps: {
        [key: string]: number
      } = {}
      if (treeConfig && treeOpts.transform) {
        const childrenField = treeOpts.children || treeOpts.childrenField
        XEUtils.eachTree(internalData.afterTreeFullData, (row, index, items, path, parentRow) => {
          const rowid = handleGetRowId(row)
          const parentRowid = handleGetRowId(parentRow)
          if (!parentRow || (expandMaps[parentRowid] && treeExpandedMaps[parentRowid])) {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              rowRest._index = fullData.length
            }
            expandMaps[rowid] = 1
            fullData.push(row)
          }
        }, { children: childrenField })
        internalData.afterFullData = fullData
        updateScrollYStatus(fullData)
        return fullData
      } else if (isRowGroupStatus) {
        const { childrenField } = aggregateOpts
        XEUtils.eachTree(internalData.afterGroupFullData, (row, index, items, path, parentRow) => {
          const rowid = handleGetRowId(row)
          const parentRowid = handleGetRowId(parentRow)
          if (!parentRow || (expandMaps[parentRowid] && rowGroupExpandedMaps[parentRowid])) {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              rowRest._index = fullData.length
            }
            expandMaps[rowid] = 1
            fullData.push(row)
          }
        }, { children: childrenField })
        internalData.afterFullData = fullData
        updateScrollYStatus(fullData)
        return fullData
      }
      return internalData.afterFullData
    }

    /**
     * 编译处理后全量的表格数据
     * 如果存在筛选条件，继续处理
     */
    const updateAfterFullData = () => {
      const { treeConfig } = props
      const { isRowGroupStatus } = reactData
      const { tableFullColumn, tableFullData, tableFullTreeData, tableFullGroupData } = internalData
      const filterOpts = computeFilterOpts.value
      const sortOpts = computeSortOpts.value
      const aggregateOpts = computeAggregateOpts.value
      const treeOpts = computeTreeOpts.value
      const childrenField = treeOpts.children || treeOpts.childrenField
      const { transform, rowField, parentField, mapChildrenField } = treeOpts
      const { isEvery, remote: allRemoteFilter, filterMethod: allFilterMethod, isDeep: isFilterDeep } = filterOpts
      const { remote: allRemoteSort, sortMethod: allSortMethod, multiple: sortMultiple, chronological, isDeep: isSortDeep } = sortOpts
      let tableData: any[] = []
      let tableTree: any[] = []
      // 处理数据
      if (!allRemoteFilter || !allRemoteSort) {
        const filterColumns: {
          column: VxeTableDefines.ColumnInfo
          valueList: any[]
          itemList: VxeTableDefines.FilterOption[]
        }[] = []
        let orderColumns: VxeTableDefines.SortCheckedParams[] = []
        tableFullColumn.forEach((column) => {
          const { field, sortable, order, filters } = column
          if (!allRemoteFilter && filters && filters.length) {
            const valueList: any[] = []
            const itemList: VxeTableDefines.FilterOption[] = []
            filters.forEach((item) => {
              if (item.checked) {
                itemList.push(item as VxeTableDefines.FilterOption)
                valueList.push(item.value)
              }
            })
            if (itemList.length) {
              filterColumns.push({ column, valueList, itemList })
            }
          }
          if (!allRemoteSort && sortable && order) {
            orderColumns.push({ column, field, property: field, order: order, sortTime: column.sortTime })
          }
        })
        if (sortMultiple && chronological && orderColumns.length > 1) {
          orderColumns = XEUtils.orderBy(orderColumns, 'sortTime')
        }

        // 处理筛选
        // 支持单列、多列、组合筛选
        if (!allRemoteFilter && filterColumns.length) {
          const handleFilter = (row: any) => {
            return filterColumns.every(({ column, valueList, itemList }) => {
              const { filterMethod, filterRender } = column
              const compConf = isEnableConf(filterRender) ? renderer.get(filterRender.name) : null
              const compFilterMethod = compConf ? (compConf.tableFilterMethod || compConf.filterMethod) : null
              const tdFilterMethod = compConf ? (compConf.tableFilterDefaultMethod || compConf.defaultTableFilterMethod || compConf.defaultFilterMethod) : null
              const cellValue = getCellValue(row, column)
              if (filterMethod) {
                return itemList.some((item) => filterMethod({ value: item.value, option: item, cellValue, row, column, $table: $xeTable }))
              } else if (compFilterMethod) {
                return itemList.some((item) => compFilterMethod({ value: item.value, option: item, cellValue, row, column, $table: $xeTable }))
              } else if (allFilterMethod) {
                return allFilterMethod({ $table: $xeTable, options: itemList, values: valueList, cellValue, row, column })
              } else if (tdFilterMethod) {
                return itemList.some((item) => tdFilterMethod({ value: item.value, option: item, cellValue, row, column, $table: $xeTable }))
              }
              return valueList.indexOf(XEUtils.get(row, column.field)) > -1
            })
          }
          if (isRowGroupStatus) {
            // 行分组
            tableTree = XEUtils.searchTree(tableFullGroupData, handleFilter, {
              original: true,
              isEvery: true,
              children: aggregateOpts.mapChildrenField,
              mapChildren: aggregateOpts.childrenField
            })
            tableData = tableTree
          } else if (treeConfig && transform && isFilterDeep !== false) {
            // 筛选虚拟树
            tableTree = XEUtils.searchTree(tableFullTreeData, handleFilter, {
              original: true,
              isEvery,
              children: mapChildrenField,
              mapChildren: childrenField
            })
            tableData = tableTree
          } else {
            tableData = treeConfig ? tableFullTreeData.filter(handleFilter) : tableFullData.filter(handleFilter)
            tableTree = tableData
          }
        } else {
          if (isRowGroupStatus) {
            // 还原行分组
            tableTree = XEUtils.searchTree(tableFullGroupData, () => true, {
              original: true,
              isEvery: true,
              children: aggregateOpts.mapChildrenField,
              mapChildren: aggregateOpts.childrenField
            })
            tableData = tableTree
          } else if (treeConfig && transform && isFilterDeep !== false) {
            // 还原虚拟树
            tableTree = XEUtils.searchTree(tableFullTreeData, () => true, {
              original: true,
              isEvery,
              children: mapChildrenField,
              mapChildren: childrenField
            })
            tableData = tableTree
          } else {
            tableData = treeConfig ? tableFullTreeData.slice(0) : tableFullData.slice(0)
            tableTree = tableData
          }
        }

        // 处理排序
        // 支持单列、多列、组合排序
        if (!allRemoteSort && orderColumns.length) {
          if (isRowGroupStatus) {
            // 行分组的排序
            if (allSortMethod) {
              const sortRests = allSortMethod({ data: tableTree, sortList: orderColumns, $table: $xeTable })
              tableTree = XEUtils.isArray(sortRests) ? sortRests : tableTree
            } else {
              const treeList = XEUtils.toTreeArray(tableTree, {
                key: aggregateOpts.rowField,
                parentKey: aggregateOpts.parentField,
                children: aggregateOpts.mapChildrenField
              })
              tableTree = XEUtils.toArrayTree(
                XEUtils.orderBy(treeList, orderColumns.map(({ column, order }) => [getOrderField(column), order])),
                {
                  key: aggregateOpts.rowField,
                  parentKey: aggregateOpts.parentField,
                  children: aggregateOpts.childrenField,
                  mapChildren: aggregateOpts.mapChildrenField
                }
              )
            }
            tableData = tableTree
          } else if (treeConfig && transform && isSortDeep !== false) {
            // 虚拟树的排序
            if (allSortMethod) {
              const sortRests = allSortMethod({ data: tableTree, sortList: orderColumns, $table: $xeTable })
              tableTree = XEUtils.isArray(sortRests) ? sortRests : tableTree
            } else {
              const treeList = XEUtils.toTreeArray(tableTree, {
                children: mapChildrenField
              })
              tableTree = XEUtils.toArrayTree(
                XEUtils.orderBy(treeList, orderColumns.map(({ column, order }) => [getOrderField(column), order])),
                {
                  key: rowField,
                  parentKey: parentField,
                  children: childrenField,
                  mapChildren: mapChildrenField
                }
              )
            }
            tableData = tableTree
          } else {
            if (allSortMethod) {
              const sortRests = allSortMethod({ data: tableData, sortList: orderColumns, $table: $xeTable })
              tableData = XEUtils.isArray(sortRests) ? sortRests : tableData
            } else {
              tableData = XEUtils.orderBy(tableData, orderColumns.map(({ column, order }) => [getOrderField(column), order]))
            }
            tableTree = tableData
          }
        }
      } else {
        if (isRowGroupStatus) {
          // 还原行分组
          // 还原虚拟树
          tableTree = XEUtils.searchTree(tableFullGroupData, () => true, {
            original: true,
            isEvery: true,
            children: aggregateOpts.mapChildrenField,
            mapChildren: aggregateOpts.childrenField
          })
          tableData = tableTree
        } else if (treeConfig && transform && isSortDeep !== false) {
          // 还原虚拟树
          tableTree = XEUtils.searchTree(tableFullTreeData, () => true, {
            original: true,
            isEvery,
            children: mapChildrenField,
            mapChildren: childrenField
          })
          tableData = tableTree
        } else {
          tableData = treeConfig ? tableFullTreeData.slice(0) : tableFullData.slice(0)
          tableTree = tableData
        }
      }
      internalData.afterFullData = tableData
      internalData.afterTreeFullData = tableTree
      internalData.afterGroupFullData = tableTree
      updateAfterDataIndex()
    }

    const updateStyle = () => {
      const { mouseConfig } = props
      const { isGroup, tableColumn, overflowX, scrollbarWidth, overflowY, scrollbarHeight, scrollXWidth, columnStore, editStore, isColLoading } = reactData
      const { visibleColumn, tableHeight, elemStore, currentRow, customHeight, customMinHeight, customMaxHeight, tHeaderHeight, tFooterHeight } = internalData
      const $xeGanttView = internalData.xeGanttView
      const el = refElem.value
      if (!el || (internalData.tBodyHeight && !el.clientHeight)) {
        return
      }
      const containerList = ['main', 'left', 'right']
      const { leftList, rightList } = columnStore
      let osbWidth = overflowY ? scrollbarWidth : 0
      let osbHeight = overflowX ? scrollbarHeight : 0
      const emptyPlaceholderElem = refEmptyPlaceholder.value
      const isHeaderRenderOptimize = computeIsHeaderRenderOptimize.value
      const isBodyRenderOptimize = computeIsBodyRenderOptimize.value
      const isFooterRenderOptimize = computeIsFooterRenderOptimize.value
      const scrollbarOpts = computeScrollbarOpts.value
      const mouseOpts = computeMouseOpts.value
      const bodyWrapperElem = getRefElem(elemStore['main-body-wrapper'])
      const bodyTableElem = getRefElem(elemStore['main-body-table'])
      if (emptyPlaceholderElem) {
        emptyPlaceholderElem.style.top = `${tHeaderHeight}px`
        emptyPlaceholderElem.style.height = bodyWrapperElem ? `${bodyWrapperElem.offsetHeight - osbHeight}px` : ''
      }

      const scrollbarXConf = scrollbarOpts.x || {}
      const scrollbarYConf = scrollbarOpts.y || {}
      const scrollbarYToLeft = computeScrollbarYToLeft.value

      let xScrollbarVisible = overflowX ? 'visible' : 'hidden'
      if (scrollbarXConf.visible === 'visible' || $xeGanttView) {
        osbHeight = scrollbarHeight
        xScrollbarVisible = 'visible'
      } else if (scrollbarXConf.visible === 'hidden' || scrollbarXConf.visible === false) {
        osbHeight = 0
        xScrollbarVisible = 'hidden'
      }

      let yScrollbarVisible = overflowY ? 'visible' : 'hidden'
      if ((scrollbarYConf.visible === 'hidden' || scrollbarYConf.visible === false) || ($xeGanttView && !scrollbarYToLeft)) {
        osbWidth = 0
        yScrollbarVisible = 'hidden'
      } else if (scrollbarYConf.visible === 'visible') {
        osbWidth = scrollbarWidth
        yScrollbarVisible = 'visible'
      }

      let tbHeight = 0
      let bodyMaxHeight = 0
      const bodyMinHeight = customMinHeight - tHeaderHeight - tFooterHeight - osbHeight
      if (customMaxHeight) {
        bodyMaxHeight = Math.max(bodyMinHeight, customMaxHeight - tHeaderHeight - tFooterHeight - osbHeight)
      }
      if (customHeight) {
        tbHeight = customHeight - tHeaderHeight - tFooterHeight - osbHeight
      }
      if (!tbHeight) {
        if (bodyTableElem) {
          tbHeight = bodyTableElem.clientHeight
        }
      }
      if (tbHeight) {
        if (bodyMaxHeight) {
          tbHeight = Math.min(bodyMaxHeight, tbHeight)
        }
        tbHeight = Math.max(bodyMinHeight, tbHeight)
      }

      const xLeftCornerEl = refScrollXLeftCornerElem.value
      const xRightCornerEl = refScrollXRightCornerElem.value
      const scrollXVirtualEl = refScrollXVirtualElem.value
      if (scrollXVirtualEl) {
        scrollXVirtualEl.style.height = `${osbHeight}px`
        scrollXVirtualEl.style.visibility = xScrollbarVisible
      }
      const xWrapperEl = refScrollXWrapperElem.value
      if (xWrapperEl) {
        xWrapperEl.style.width = `${el.clientWidth - osbWidth}px`
        if (scrollbarYToLeft) {
          xWrapperEl.style.left = `${osbWidth}px`
        } else {
          xWrapperEl.style.left = ''
        }
      }
      if (xLeftCornerEl) {
        if (scrollbarYToLeft) {
          xLeftCornerEl.style.width = `${osbWidth}px`
          xLeftCornerEl.style.display = overflowY && osbWidth ? 'block' : ''
        } else {
          xLeftCornerEl.style.display = ''
        }
      }
      if (xRightCornerEl) {
        if (scrollbarYToLeft) {
          xRightCornerEl.style.display = ''
        } else {
          xRightCornerEl.style.width = `${osbWidth}px`
          xRightCornerEl.style.display = xScrollbarVisible === 'visible' ? 'block' : ''
        }
      }

      const scrollYVirtualEl = refScrollYVirtualElem.value
      if (scrollYVirtualEl) {
        scrollYVirtualEl.style.width = `${osbWidth}px`
        scrollYVirtualEl.style.height = `${tbHeight + tHeaderHeight + tFooterHeight}px`
        scrollYVirtualEl.style.visibility = yScrollbarVisible
      }
      const yTopCornerEl = refScrollYTopCornerElem.value
      if (yTopCornerEl) {
        yTopCornerEl.style.height = `${tHeaderHeight}px`
        yTopCornerEl.style.display = tHeaderHeight && yScrollbarVisible === 'visible' ? 'block' : ''
      }
      const yWrapperEl = refScrollYWrapperElem.value
      if (yWrapperEl) {
        yWrapperEl.style.height = `${tbHeight}px`
        yWrapperEl.style.top = `${tHeaderHeight}px`
      }
      const yBottomCornerEl = refScrollYBottomCornerElem.value
      if (yBottomCornerEl) {
        yBottomCornerEl.style.height = `${tFooterHeight}px`
        yBottomCornerEl.style.top = `${tHeaderHeight + tbHeight}px`
        yBottomCornerEl.style.display = tFooterHeight && yScrollbarVisible === 'visible' ? 'block' : ''
      }

      const rowExpandEl = refRowExpandElem.value
      if (rowExpandEl) {
        rowExpandEl.style.height = `${tbHeight}px`
        rowExpandEl.style.top = `${tHeaderHeight}px`
      }

      internalData.tBodyHeight = tbHeight

      containerList.forEach((name, index) => {
        const fixedType = index > 0 ? name : ''
        const layoutList = ['header', 'body', 'footer']
        const isFixedLeft = fixedType === 'left'
        let fixedColumn: VxeTableDefines.ColumnInfo[] = []
        let fixedWrapperElem: HTMLDivElement
        if (fixedType) {
          fixedColumn = isFixedLeft ? leftList : rightList
          fixedWrapperElem = isFixedLeft ? refLeftContainer.value : refRightContainer.value
        }
        layoutList.forEach(layout => {
          const wrapperElem = getRefElem(elemStore[`${name}-${layout}-wrapper`])
          const currScrollElem = getRefElem(elemStore[`${name}-${layout}-scroll`])
          const tableElem = getRefElem(elemStore[`${name}-${layout}-table`])
          if (layout === 'header') {
            // 表头体样式处理
            // 横向滚动渲染
            let renderColumnList = tableColumn
            const isOptimizeMode = isHeaderRenderOptimize

            if (isGroup) {
              renderColumnList = visibleColumn
            } else {
              if (!isOptimizeMode || (!isColLoading && (fixedType || !overflowX))) {
                renderColumnList = visibleColumn
              }

              if (fixedType) {
                // 如果是使用优化模式
                if (isOptimizeMode) {
                  renderColumnList = fixedColumn || []
                }
              }
            }

            const tWidth = renderColumnList.reduce((previous, column) => previous + column.renderWidth, 0)

            if (fixedType) {
              if (isGroup) {
                if (wrapperElem) {
                  wrapperElem.style.width = scrollXWidth ? `${scrollXWidth}px` : ''
                }
              } else {
                if (isOptimizeMode) {
                  if (wrapperElem) {
                    wrapperElem.style.width = tWidth ? `${tWidth}px` : ''
                  }
                } else {
                  if (wrapperElem) {
                    wrapperElem.style.width = scrollXWidth ? `${scrollXWidth}px` : ''
                  }
                }
              }
            }

            if (currScrollElem) {
              currScrollElem.style.height = `${tHeaderHeight}px`
            }

            if (tableElem) {
              tableElem.style.width = tWidth ? `${tWidth}px` : ''
            }
          } else if (layout === 'body') {
            if (currScrollElem) {
              currScrollElem.style.maxHeight = customMaxHeight ? `${bodyMaxHeight}px` : ''
              currScrollElem.style.height = customHeight ? `${tbHeight}px` : ''
              currScrollElem.style.minHeight = `${bodyMinHeight}px`
            }

            // 如果是固定列
            if (fixedWrapperElem) {
              if (wrapperElem) {
                wrapperElem.style.top = `${tHeaderHeight}px`
              }
              fixedWrapperElem.style.height = `${customHeight > 0 ? customHeight : (tableHeight + tHeaderHeight + tFooterHeight + osbHeight)}px`
              fixedWrapperElem.style.width = `${fixedColumn.reduce((previous, column) => previous + column.renderWidth, 0)}px`
            }

            let renderColumnList = tableColumn
            const isOptimizeMode = isBodyRenderOptimize

            if (fixedType) {
              renderColumnList = visibleColumn
              if (isOptimizeMode) {
                renderColumnList = fixedColumn || []
              }
            }

            const tWidth = renderColumnList.reduce((previous, column) => previous + column.renderWidth, 0)

            if (fixedType) {
              if (isOptimizeMode) {
                if (wrapperElem) {
                  wrapperElem.style.width = tWidth ? `${tWidth}px` : ''
                }
              } else {
                if (wrapperElem) {
                  wrapperElem.style.width = scrollXWidth ? `${scrollXWidth}px` : ''
                }
              }
            }

            if (tableElem) {
              tableElem.style.width = tWidth ? `${tWidth}px` : ''
              // 兼容性处理
              tableElem.style.paddingRight = osbWidth && fixedType && (browseObj.firefox || browseObj.safari) ? `${osbWidth}px` : ''
            }
            const emptyBlockElem = getRefElem(elemStore[`${name}-${layout}-emptyBlock`])
            if (emptyBlockElem) {
              emptyBlockElem.style.width = tWidth ? `${tWidth}px` : ''
            }
          } else if (layout === 'footer') {
            let renderColumnList = tableColumn
            const isOptimizeMode = isFooterRenderOptimize

            if (!isOptimizeMode || (!isColLoading && (fixedType || !overflowX))) {
              renderColumnList = visibleColumn
            }

            if (fixedType) {
              if (isOptimizeMode) {
                renderColumnList = fixedColumn || []
              }
            }

            const tWidth = renderColumnList.reduce((previous, column) => previous + column.renderWidth, 0)

            if (fixedType) {
              if (isOptimizeMode) {
                if (wrapperElem) {
                  wrapperElem.style.width = tWidth ? `${tWidth}px` : ''
                }
              } else {
                if (wrapperElem) {
                  wrapperElem.style.width = scrollXWidth ? `${scrollXWidth}px` : ''
                }
              }
            }

            if (currScrollElem) {
              currScrollElem.style.height = `${tFooterHeight}px`
              // 如果是固定列
              if (fixedWrapperElem) {
                if (wrapperElem) {
                  wrapperElem.style.top = `${customHeight > 0 ? customHeight - tFooterHeight - osbHeight : tableHeight + tHeaderHeight}px`
                }
              }
            }
            if (tableElem) {
              tableElem.style.width = tWidth ? `${tWidth}px` : ''
            }
          }
        })
      })
      if (currentRow) {
        $xeTable.setCurrentRow(currentRow)
      }
      if (mouseConfig && mouseOpts.selected && editStore.selected.row && editStore.selected.column) {
        $xeTable.addCellSelectedClass()
      }
      if ($xeGanttView && $xeGanttView.handleUpdateStyle) {
        $xeGanttView.handleUpdateStyle()
      }
      return nextTick()
    }

    const checkValidate = (type: any) => {
      if ($xeTable.triggerValidate) {
        return $xeTable.triggerValidate(type)
      }
      return nextTick()
    }

    /**
     * 当单元格发生改变时
     * 如果存在规则，则校验
     */
    const handleChangeCell = (evnt: Event, params: any) => {
      checkValidate('blur')
        .catch((e: any) => e)
        .then(() => {
          $xeTable.handleEdit(params, evnt)
            .then(() => checkValidate('change'))
            .catch((e: any) => e)
        })
    }

    const handleDefaultSort = () => {
      const { sortConfig } = props
      if (sortConfig) {
        const sortOpts = computeSortOpts.value
        let { defaultSort } = sortOpts
        if (defaultSort) {
          if (!XEUtils.isArray(defaultSort)) {
            defaultSort = [defaultSort]
          }
          if (defaultSort.length) {
            (sortConfig.multiple ? defaultSort : defaultSort.slice(0, 1)).forEach((item: any, index: number) => {
              const { field, order } = item
              if (field && order) {
                const column = $xeTable.getColumnByField(field)
                if (column && column.sortable) {
                  column.order = order
                  column.sortTime = Date.now() + index
                }
              }
            })
            if (!sortOpts.remote) {
              $xeTable.handleTableData(true).then(updateStyle)
            }
          }
        }
      }
    }

    /**
     * 处理默认勾选
     */
    const handleDefaultSelectionChecked = () => {
      const { checkboxConfig } = props
      if (checkboxConfig) {
        const { fullDataRowIdData } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkAll, checkRowKeys } = checkboxOpts
        if (checkAll) {
          handleCheckedAllCheckboxRow(true, true)
        } else if (checkRowKeys) {
          const defSelection: any[] = []
          checkRowKeys.forEach((rowid: any) => {
            if (fullDataRowIdData[rowid]) {
              defSelection.push(fullDataRowIdData[rowid].row)
            }
          })
          handleCheckedCheckboxRow(defSelection, true, true)
        }
      }
    }

    /**
     * 处理单选框默认勾选
     */
    const handleDefaultRadioChecked = () => {
      const { radioConfig } = props
      if (radioConfig) {
        const { fullDataRowIdData } = internalData
        const radioOpts = computeRadioOpts.value
        const { checkRowKey: rowid, reserve } = radioOpts
        if (rowid) {
          if (fullDataRowIdData[rowid]) {
            handleCheckedRadioRow(fullDataRowIdData[rowid].row, true)
          }
          if (reserve) {
            const rowkey = getRowkey($xeTable)
            internalData.radioReserveRow = { [rowkey]: rowid }
          }
        }
      }
    }

    /**
     * 处理默认展开行
     */
    const handleDefaultRowExpand = () => {
      const { expandConfig } = props
      if (expandConfig) {
        const { fullDataRowIdData } = internalData
        const expandOpts = computeExpandOpts.value
        const { expandAll, expandRowKeys } = expandOpts
        if (expandAll) {
          $xeTable.setAllRowExpand(true)
        } else if (expandRowKeys) {
          const defExpandeds: any[] = []
          expandRowKeys.forEach((rowid: any) => {
            if (fullDataRowIdData[rowid]) {
              defExpandeds.push(fullDataRowIdData[rowid].row)
            }
          })
          $xeTable.setRowExpand(defExpandeds, true)
        }
      }
    }

    const handleRadioReserveRow = (row: any) => {
      const radioOpts = computeRadioOpts.value
      if (radioOpts.reserve) {
        internalData.radioReserveRow = row
      }
    }

    const handleCheckboxReserveRow = (row: any, checked: boolean) => {
      const { checkboxReserveRowMap } = internalData
      const checkboxOpts = computeCheckboxOpts.value
      if (checkboxOpts.reserve) {
        const rowid = getRowid($xeTable, row)
        if (checked) {
          checkboxReserveRowMap[rowid] = row
        } else if (checkboxReserveRowMap[rowid]) {
          delete checkboxReserveRowMap[rowid]
        }
      }
    }

    const handleCheckedRadioRow = (row: any, isForce: boolean) => {
      const radioOpts = computeRadioOpts.value
      const { checkMethod } = radioOpts
      if (row && (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row })))) {
        reactData.selectRadioRow = row
        handleRadioReserveRow(row)
      }
      return nextTick()
    }

    const handleCheckedCheckboxRow = (rows: any[], value: boolean, isForce?: boolean) => {
      if (rows && !XEUtils.isArray(rows)) {
        rows = [rows]
      }
      $xeTable.handleBatchSelectRows(rows, !!value, isForce)
      $xeTable.checkSelectionStatus()
      return nextTick()
    }

    const handleCheckedAllCheckboxRow = (checked: boolean, isForce?: boolean) => {
      const { treeConfig } = props
      const { isRowGroupStatus } = reactData
      const { afterFullData, afterTreeFullData, afterGroupFullData, checkboxReserveRowMap, selectCheckboxMaps } = internalData
      const treeOpts = computeTreeOpts.value
      const aggregateOpts = computeAggregateOpts.value
      const childrenField = treeOpts.children || treeOpts.childrenField
      const checkboxOpts = computeCheckboxOpts.value
      const { checkField, reserve, checkMethod } = checkboxOpts
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      // indeterminateField 仅支持读取
      const indeterminateField = checkboxOpts.indeterminateField || checkboxOpts.halfField
      const selectRowMaps: Record<string, any> = {}

      /**
       * 绑定属性方式（有污染）
       * 必须在行数据存在对应的属性，否则将不响应
       */
      if (checkField) {
        const checkValFn = (row: any) => {
          if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
            if (checked) {
              selectRowMaps[handleGetRowId(row)] = row
            }
            XEUtils.set(row, checkField, checked)
          }
          if ((treeConfig || isRowGroupStatus) && indeterminateField) {
            XEUtils.set(row, indeterminateField, false)
          }
        }
        // 如果存在选中方法
        // 如果方法成立，则更新值，否则忽略该数据
        if ((treeConfig || isRowGroupStatus)) {
          XEUtils.eachTree(afterFullData, checkValFn, { children: childrenField })
        } else {
          afterFullData.forEach(checkValFn)
        }
      } else {
        /**
         * 默认方式（无污染）
         * 无需任何属性，直接绑定
         */
        if (isRowGroupStatus) {
          if (checked) {
            /**
             * 如果是行分组勾选
             * 如果方法成立，则添加到临时集合中
             */
            XEUtils.eachTree(afterGroupFullData, (row) => {
              if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
                const rowid = handleGetRowId(row)
                selectRowMaps[rowid] = row
              }
            }, { children: aggregateOpts.mapChildrenField })
          } else {
            /**
             * 如果是树取消
             * 如果方法成立，则不添加到临时集合中
             */
            if (!isForce && checkMethod) {
              XEUtils.eachTree(afterGroupFullData, (row) => {
                const rowid = handleGetRowId(row)
                if (checkMethod({ $table: $xeTable, row }) ? false : selectCheckboxMaps[rowid]) {
                  selectRowMaps[rowid] = row
                }
              }, { children: aggregateOpts.mapChildrenField })
            }
          }
        } else if (treeConfig) {
          if (checked) {
            /**
             * 如果是树勾选
             * 如果方法成立，则添加到临时集合中
             */
            XEUtils.eachTree(afterTreeFullData, (row) => {
              if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
                const rowid = handleGetRowId(row)
                selectRowMaps[rowid] = row
              }
            }, { children: childrenField })
          } else {
            /**
             * 如果是树取消
             * 如果方法成立，则不添加到临时集合中
             */
            if (!isForce && checkMethod) {
              XEUtils.eachTree(afterTreeFullData, (row) => {
                const rowid = handleGetRowId(row)
                if (checkMethod({ $table: $xeTable, row }) ? false : selectCheckboxMaps[rowid]) {
                  selectRowMaps[rowid] = row
                }
              }, { children: childrenField })
            }
          }
        } else {
          if (checked) {
            /**
             * 如果是行勾选
             * 如果存在选中方法且成立或者本身已勾选，则添加到临时集合中
             * 如果不存在选中方法，则添加所有数据到临时集合中
             */
            if (!isForce && checkMethod) {
              afterFullData.forEach((row) => {
                const rowid = handleGetRowId(row)
                if (selectCheckboxMaps[rowid] || checkMethod({ $table: $xeTable, row })) {
                  selectRowMaps[rowid] = row
                }
              })
            } else {
              afterFullData.forEach(row => {
                const rowid = handleGetRowId(row)
                selectRowMaps[rowid] = row
              })
            }
          } else {
            /**
             * 如果是行取消
             * 如果方法成立，则不添加到临时集合中；如果方法不成立则判断当前是否已勾选，如果已被勾选则添加到新集合中
             * 如果不存在选中方法，无需处理，临时集合默认为空
             */
            if (!isForce && checkMethod) {
              afterFullData.forEach((row) => {
                const rowid = handleGetRowId(row)
                if (checkMethod({ $table: $xeTable, row }) ? false : selectCheckboxMaps[rowid]) {
                  selectRowMaps[rowid] = row
                }
              })
            }
          }
        }
      }
      if (reserve) {
        if (checked) {
          XEUtils.each(selectRowMaps, (row, rowid) => {
            checkboxReserveRowMap[rowid] = row
          })
        } else {
          afterFullData.forEach((row) => handleCheckboxReserveRow(row, false))
        }
      }
      reactData.updateCheckboxFlag++
      internalData.selectCheckboxMaps = checkField ? {} : selectRowMaps

      reactData.isAllSelected = checked
      reactData.isIndeterminate = false
      internalData.treeIndeterminateRowMaps = {}
      $xeTable.checkSelectionStatus()
      return nextTick()
    }

    // 还原展开、选中等相关状态
    const handleReserveStatus = () => {
      const { treeConfig } = props
      const { expandColumn, selectRadioRow } = reactData
      const { fullDataRowIdData, fullAllDataRowIdData, radioReserveRow, selectCheckboxMaps, treeExpandedMaps, rowExpandedMaps, currentRow } = internalData
      const expandOpts = computeExpandOpts.value
      const treeOpts = computeTreeOpts.value
      const radioOpts = computeRadioOpts.value
      const checkboxOpts = computeCheckboxOpts.value
      // 单选框
      if (selectRadioRow && !fullAllDataRowIdData[getRowid($xeTable, selectRadioRow)]) {
        reactData.selectRadioRow = null // 刷新单选行状态
      }
      // 还原保留选中状态
      if (radioOpts.reserve && radioReserveRow) {
        const rowid = getRowid($xeTable, radioReserveRow)
        if (fullDataRowIdData[rowid]) {
          handleCheckedRadioRow(fullDataRowIdData[rowid].row, true)
        }
      }
      // 复选框
      internalData.selectCheckboxMaps = getRecoverRowMaps(selectCheckboxMaps) // 刷新多选行状态
      reactData.updateCheckboxFlag++
      // 还原保留选中状态
      if (checkboxOpts.reserve) {
        handleCheckedCheckboxRow(handleReserveRow(internalData.checkboxReserveRowMap), true, true)
      }
      if (currentRow && !fullAllDataRowIdData[getRowid($xeTable, currentRow)]) {
        internalData.currentRow = null // 刷新当前行状态
      }
      // 行展开
      internalData.rowExpandedMaps = expandColumn ? getRecoverRowMaps(rowExpandedMaps) : {} // 刷新行展开状态
      reactData.rowExpandedFlag++
      // 还原保留状态
      if (expandColumn && expandOpts.reserve) {
        $xeTable.setRowExpand(handleReserveRow(internalData.rowExpandedReserveRowMap), true)
      }
      // 树展开
      internalData.treeExpandedMaps = treeConfig ? getRecoverRowMaps(treeExpandedMaps) : {} // 刷新树展开状态
      reactData.treeExpandedFlag++
      if (treeConfig && treeOpts.reserve) {
        $xeTable.setTreeExpand(handleReserveRow(internalData.treeExpandedReserveRowMap), true)
      }
    }

    /**
     * 处理默认展开树节点
     */
    const handleDefaultTreeExpand = () => {
      const { treeConfig } = props
      if (treeConfig) {
        const { fullAllDataRowIdData } = internalData
        const treeOpts = computeTreeOpts.value
        const { expandAll, expandRowKeys } = treeOpts
        if (expandAll) {
          $xeTable.setAllTreeExpand(true)
        } else if (expandRowKeys) {
          const defExpandeds: any[] = []
          expandRowKeys.forEach((rowid) => {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              defExpandeds.push(rowRest.row)
            }
          })
          $xeTable.setTreeExpand(defExpandeds, true)
        }
      }
    }

    const handleAsyncTreeExpandChilds = (row: any): Promise<void> => {
      const treeOpts = computeTreeOpts.value
      const checkboxOpts = computeCheckboxOpts.value
      const { transform, loadMethod } = treeOpts
      const { checkStrictly } = checkboxOpts
      return new Promise<void>(resolve => {
        if (loadMethod) {
          const { fullAllDataRowIdData, treeExpandLazyLoadedMaps } = internalData
          const rowid = getRowid($xeTable, row)
          const rowRest = fullAllDataRowIdData[rowid]
          treeExpandLazyLoadedMaps[rowid] = row
          Promise.resolve(
            loadMethod({ $table: $xeTable, row })
          ).then((childRecords: any) => {
            if (rowRest) {
              rowRest.treeLoaded = true
            }
            if (treeExpandLazyLoadedMaps[rowid]) {
              delete treeExpandLazyLoadedMaps[rowid]
            }
            if (!XEUtils.isArray(childRecords)) {
              childRecords = []
            }
            if (childRecords) {
              return $xeTable.loadTreeChildren(row, childRecords).then(childRows => {
                const { treeExpandedMaps } = internalData
                if (childRows.length && !treeExpandedMaps[rowid]) {
                  treeExpandedMaps[rowid] = row
                }
                reactData.treeExpandedFlag++
                // 如果当前节点已选中，则展开后子节点也被选中
                if (!checkStrictly && $xeTable.isCheckedByCheckboxRow(row)) {
                  handleCheckedCheckboxRow(childRows, true)
                }
                return nextTick().then(() => {
                  if (transform) {
                    $xeTable.handleTableData()
                    updateAfterDataIndex()
                    return nextTick()
                  }
                })
              })
            }
          }).catch(() => {
            const { treeExpandLazyLoadedMaps } = internalData
            if (rowRest) {
              rowRest.treeLoaded = false
            }
            if (treeExpandLazyLoadedMaps[rowid]) {
              delete treeExpandLazyLoadedMaps[rowid]
            }
          }).finally(() => {
            reactData.treeExpandedFlag++
            nextTick().then(() => $xeTable.recalculate()).then(() => resolve())
          })
        } else {
          resolve()
        }
      })
    }

    const handleTreeExpandReserve = (row: any, expanded: boolean) => {
      const { treeExpandedReserveRowMap } = internalData
      const treeOpts = computeTreeOpts.value
      if (treeOpts.reserve) {
        const rowid = getRowid($xeTable, row)
        if (expanded) {
          treeExpandedReserveRowMap[rowid] = row
        } else if (treeExpandedReserveRowMap[rowid]) {
          delete treeExpandedReserveRowMap[rowid]
        }
      }
    }

    const handleAsyncRowExpand = (row: any): Promise<void> => {
      return new Promise<void>(resolve => {
        const expandOpts = computeExpandOpts.value
        const { loadMethod } = expandOpts
        if (loadMethod) {
          const { fullAllDataRowIdData, rowExpandLazyLoadedMaps } = internalData
          const rowid = getRowid($xeTable, row)
          const rowRest = fullAllDataRowIdData[rowid]
          rowExpandLazyLoadedMaps[rowid] = row
          loadMethod({ $table: $xeTable, row, rowIndex: $xeTable.getRowIndex(row), $rowIndex: $xeTable.getVMRowIndex(row) }).then(() => {
            const { rowExpandedMaps } = internalData
            if (rowRest) {
              rowRest.expandLoaded = true
            }
            rowExpandedMaps[rowid] = row
            reactData.rowExpandedFlag++
          }).catch(() => {
            if (rowRest) {
              rowRest.expandLoaded = false
            }
          }).finally(() => {
            const { rowExpandLazyLoadedMaps } = internalData
            if (rowExpandLazyLoadedMaps[rowid]) {
              delete rowExpandLazyLoadedMaps[rowid]
            }
            reactData.rowExpandedFlag++
            nextTick()
              .then(() => $xeTable.recalculate())
              .then(() => $xeTable.updateCellAreas())
              .then(() => resolve())
          })
        } else {
          resolve()
        }
      })
    }

    const handleRowExpandReserve = (row: any, expanded: boolean) => {
      const { rowExpandedReserveRowMap } = internalData
      const expandOpts = computeExpandOpts.value
      if (expandOpts.reserve) {
        const rowid = getRowid($xeTable, row)
        if (expanded) {
          rowExpandedReserveRowMap[rowid] = row
        } else if (rowExpandedReserveRowMap[rowid]) {
          delete rowExpandedReserveRowMap[rowid]
        }
      }
    }

    const handleDefaultMergeCells = () => {
      const { mergeCells } = props
      if (mergeCells) {
        $xeTable.setMergeCells(mergeCells)
      }
    }

    const handleDefaultMergeHeaderItems = () => {
      const { mergeHeaderCells } = props
      if (mergeHeaderCells) {
        $xeTable.setMergeHeaderCells(mergeHeaderCells)
      }
    }

    const handleDefaultMergeFooterItems = () => {
      const { mergeFooterCells, mergeFooterItems } = props
      const mFooterCells = mergeFooterCells || mergeFooterItems
      if (mFooterCells) {
        $xeTable.setMergeFooterCells(mFooterCells)
      }
    }

    // 计算可视渲染相关数据
    const computeScrollLoad = () => {
      return nextTick().then(() => {
        const { scrollXLoad, scrollYLoad } = reactData
        const { scrollXStore, scrollYStore } = internalData
        const virtualYOpts = computeVirtualYOpts.value
        const virtualXOpts = computeVirtualXOpts.value
        // 计算 X 逻辑
        if (scrollXLoad) {
          const { toVisibleIndex: toXVisibleIndex, visibleSize: visibleXSize } = handleVirtualXVisible()
          const offsetXSize = Math.max(0, virtualXOpts.oSize ? XEUtils.toNumber(virtualXOpts.oSize) : 0)
          scrollXStore.preloadSize = XEUtils.toNumber(virtualXOpts.preSize)
          scrollXStore.offsetSize = offsetXSize
          scrollXStore.visibleSize = visibleXSize
          scrollXStore.endIndex = Math.max(scrollXStore.startIndex + scrollXStore.visibleSize + offsetXSize, scrollXStore.endIndex)
          scrollXStore.visibleStartIndex = Math.max(scrollXStore.startIndex, toXVisibleIndex)
          scrollXStore.visibleEndIndex = Math.min(scrollXStore.endIndex, toXVisibleIndex + visibleXSize)
          $xeTable.updateScrollXData().then(() => {
            loadScrollXData()
          })
        } else {
          $xeTable.updateScrollXSpace()
        }
        // 计算 Y 逻辑
        const rowHeight = computeRowHeight()

        ;(scrollYStore as any).rowHeight = rowHeight // 已废弃

        reactData.rowHeight = rowHeight
        const { toVisibleIndex: toYVisibleIndex, visibleSize: visibleYSize } = handleVirtualYVisible()
        if (scrollYLoad) {
          const offsetYSize = Math.max(0, virtualYOpts.oSize ? XEUtils.toNumber(virtualYOpts.oSize) : 0)
          scrollYStore.preloadSize = XEUtils.toNumber(virtualYOpts.preSize)
          scrollYStore.offsetSize = offsetYSize
          scrollYStore.visibleSize = visibleYSize
          scrollYStore.endIndex = Math.max(scrollYStore.startIndex + visibleYSize + offsetYSize, scrollYStore.endIndex)
          scrollYStore.visibleStartIndex = Math.max(scrollYStore.startIndex, toYVisibleIndex)
          scrollYStore.visibleEndIndex = Math.min(scrollYStore.endIndex, toYVisibleIndex + visibleYSize)
          $xeTable.updateScrollYData().then(() => {
            loadScrollYData()
          })
        } else {
          $xeTable.updateScrollYSpace()
        }
      })
    }

    const calcScrollbar = () => {
      const { scrollXWidth, scrollYHeight } = reactData
      const { elemStore } = internalData
      const scrollbarOpts = computeScrollbarOpts.value
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      const bodyWrapperElem = getRefElem(elemStore['main-body-wrapper'])
      const headerTableElem = getRefElem(elemStore['main-header-table'])
      const footerTableElem = getRefElem(elemStore['main-footer-table'])
      const xHandleEl = refScrollXHandleElem.value
      const yHandleEl = refScrollYHandleElem.value
      let overflowY = false
      let overflowX = false
      if (bodyWrapperElem) {
        overflowY = scrollYHeight > bodyWrapperElem.clientHeight
        if (yHandleEl) {
          reactData.scrollbarWidth = scrollbarOpts.width || (yHandleEl.offsetWidth - yHandleEl.clientWidth) || 14
        }
        reactData.overflowY = overflowY

        overflowX = scrollXWidth > bodyWrapperElem.clientWidth
        if (xHandleEl) {
          reactData.scrollbarHeight = scrollbarOpts.height || (xHandleEl.offsetHeight - xHandleEl.clientHeight) || 14
        }

        const hHeight = headerTableElem ? headerTableElem.clientHeight : 0
        const fHeight = footerTableElem ? footerTableElem.clientHeight : 0
        internalData.tableHeight = bodyWrapperElem.offsetHeight
        internalData.tHeaderHeight = hHeight
        internalData.tFooterHeight = fHeight
        reactData.overflowX = overflowX
        reactData.parentHeight = Math.max(hHeight + fHeight + 20, $xeTable.getParentHeight())
      }
      if (overflowX) {
        $xeTable.checkScrolling()
      }
    }

    const handleRecalculateStyle = (reFull: boolean, reWidth: boolean, reHeight: boolean) => {
      const el = refElem.value
      internalData.rceRunTime = Date.now()
      if (!el || !el.clientWidth) {
        return nextTick()
      }
      const varEl = refVarElem.value
      if (varEl) {
        const [defEl, mediumEl, smallEl, miniEl] = varEl.children
        calcVarRowHeightConfig('default', defEl)
        calcVarRowHeightConfig('medium', mediumEl)
        calcVarRowHeightConfig('small', smallEl)
        calcVarRowHeightConfig('mini', miniEl)
      }
      if (reWidth) {
        calcCellWidth()
      }
      if (reFull) {
        autoCellWidth()
      }
      calcScrollbar()
      updateStyle()
      updateRowExpandStyle()
      if (reFull) {
        updateTreeLineStyle()
      }
      return computeScrollLoad().then(() => {
        // 初始化时需要在列计算之后再执行优化运算，达到最优显示效果
        if (reWidth) {
          calcCellWidth()
        }
        if (reFull) {
          autoCellWidth()
        }
        if (reHeight) {
          calcCellHeight()
        }
        updateStyle()
        calcScrollbar()
        if (reFull) {
          updateRowOffsetTop()
        }
        updateRowExpandStyle()
        if (reFull) {
          updateTreeLineStyle()
        }
        if (reFull) {
          return computeScrollLoad()
        }
      })
    }

    const handleLazyRecalculate = (reFull: boolean, reWidth: boolean, reHeight: boolean) => {
      return new Promise<void>(resolve => {
        const $xeGanttView = internalData.xeGanttView
        const { customStore } = reactData
        const { rceTimeout, rceRunTime } = internalData
        const resizeOpts = computeResizeOpts.value
        const refreshDelay = resizeOpts.refreshDelay || 20
        const el = refElem.value
        if (el && el.clientWidth) {
          autoCellWidth()
          updateRowExpandStyle()
        }
        if (customStore.visible && $xeTable.handleCustomStyle) {
          $xeTable.handleCustomStyle()
        }
        if (rceTimeout) {
          clearTimeout(rceTimeout)
          if (rceRunTime && rceRunTime + (refreshDelay - 5) < Date.now()) {
            resolve(
              handleRecalculateStyle(reFull, reWidth, reHeight)
            )
          } else {
            nextTick(() => {
              resolve()
            })
          }
        } else {
          resolve(
            handleRecalculateStyle(reFull, reWidth, reHeight)
          )
        }
        if ($xeGanttView && $xeGanttView.handleLazyRecalculate) {
          $xeGanttView.handleLazyRecalculate()
        }
        internalData.rceTimeout = setTimeout(() => {
          internalData.rceTimeout = undefined
          handleRecalculateStyle(reFull, reWidth, reHeight)
        }, refreshDelay)
      })
    }

    const handleResizeEvent = () => {
      handleLazyRecalculate(true, true, true)
    }

    const handleUpdateAggValues = () => {
      const { visibleColumn } = internalData
      const aggCols: VxeTableDefines.ColumnInfo[] = []
      visibleColumn.forEach(column => {
        if (column.aggFunc) {
          aggCols.push(column)
        }
      })
      reactData.aggHandleAggColumns = aggCols
    }

    const handleUpdateRowGroup = (groupFields?: string[]) => {
      const aggGroupFields: string[] = []
      const aggGroupConfs: { field: string }[] = []
      if (groupFields) {
        (XEUtils.isArray(groupFields) ? groupFields : [groupFields]).forEach(field => {
          aggGroupFields.push(field)
          aggGroupConfs.push({
            field
          })
        })
      }
      reactData.rowGroupList = aggGroupConfs
      reactData.aggHandleFields = aggGroupFields
      handleUpdateAggValues()
    }

    const handleeGroupSummary = (aggList: VxeTableDefines.AggregateRowInfo[]) => {
      const { fullColumnFieldData } = internalData
      const aggregateOpts = computeAggregateOpts.value
      const aggFuncColumns = computeAggFuncColumns.value
      const { mapChildrenField } = aggregateOpts
      const aggCalcMethod = aggregateOpts.calcValuesMethod || aggregateOpts.countMethod || aggregateOpts.aggregateMethod
      if (mapChildrenField) {
        XEUtils.lastEach(aggList, aggRow => {
          let count = 0
          XEUtils.each(aggRow[mapChildrenField], (row: VxeTableDefines.AggregateRowInfo) => {
            if (row.isAggregate) {
              count += row.childCount || 0
            } else {
              count++
            }
          })
          aggRow.childCount = count
        })
        if ($xeTable.handlePivotTableAggregateData) {
          $xeTable.handlePivotTableAggregateData(aggList)
        } else {
          if (aggFuncColumns.length) {
            XEUtils.lastEach(aggList, aggRow => {
              const aggDtObj: Record<string, {
              type: string
              value: any
              label: any
            }> = {}
              const aggData = aggRow.aggData
              const groupField = aggRow.groupField
              const groupContent = aggRow.groupContent
              const childList = mapChildrenField ? (aggRow[mapChildrenField] || []) : []
              const childCount = aggRow.childCount
              const colRest = fullColumnFieldData[groupField] || {}
              aggFuncColumns.forEach(column => {
                const { field } = column
                const currAggData = aggData ? aggData[field] : null
                const ctParams = {
                  $table: $xeTable,
                  groupField,
                  groupColumn: (colRest ? colRest.column : null) as VxeTableDefines.ColumnInfo,
                  column,
                  groupValue: groupContent,
                  childList,
                  childCount,
                  aggValue: currAggData ? currAggData.value : 0,

                  /**
                 * 已废弃
                 * @deprecated
                 */
                  children: childList,
                  /**
                 * 已废弃
                 * @deprecated
                 */
                  totalValue: childCount
                }
                let aggVal = 0
                // 如果下层同时也是分组
                if (childList.length && childList[0].isAggregate) {
                  XEUtils.each(childList, (row: VxeTableDefines.AggregateRowInfo) => {
                    if (row.isAggregate) {
                      const currAggData = row.aggData[field]
                      if (currAggData) {
                        aggVal += currAggData.value
                      }
                    }
                  })
                } else {
                  aggVal = aggCalcMethod ? aggCalcMethod(ctParams) : aggRow.childCount
                }
                aggDtObj[field] = {
                  type: 'count',
                  value: aggVal,
                  label: aggVal
                }
              })
              aggRow.aggData = aggDtObj
            })
          }
        }
      }
    }

    const updateGroupData = () => {
      const { aggregateConfig, rowGroupConfig } = props
      const { isRowGroupStatus } = reactData
      const { tableFullGroupData } = internalData
      const aggregateOpts = computeAggregateOpts.value
      const { mapChildrenField } = aggregateOpts
      if ((aggregateConfig || rowGroupConfig) && isRowGroupStatus) {
        const aggList: VxeTableDefines.AggregateRowInfo[] = []
        XEUtils.eachTree(tableFullGroupData, row => {
          if (row.isAggregate) {
            aggList.push(row)
          }
        }, { children: mapChildrenField })
        handleeGroupSummary(aggList)
      }
    }

    const handleGroupData = (list: any[], rowGroups: VxeTableDefines.RowGroupItem[]) => {
      let fullData = list
      let treeData = list
      if (rowGroups) {
        const aggregateOpts = computeAggregateOpts.value
        const { rowField, parentField, childrenField, mapChildrenField } = aggregateOpts
        const rowOpts = computeRowOpts.value
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField } = checkboxOpts
        const indeterminateField = checkboxOpts.indeterminateField || checkboxOpts.halfField
        const rgItem = rowGroups[0]
        if (rgItem && rowField && parentField && childrenField && mapChildrenField) {
          fullData = []
          treeData = []
          const groupField = rgItem.field
          const groupColumn = $xeTable.getColumnByField(groupField)
          const groupMaps: Record<string, any[]> = {}
          const aggList: VxeTableDefines.AggregateRowInfo[] = []
          const rowkey = getRowkey($xeTable)
          list.forEach((row) => {
            const cellValue = groupColumn ? $xeTable.getCellLabel(row, groupColumn) : XEUtils.get(row, groupField)
            const groupValue = XEUtils.eqNull(cellValue) ? '' : cellValue
            let childList = groupMaps[groupValue]
            if (!childList) {
              childList = []
              groupMaps[groupValue] = childList
            }
            if (row.isAggregate) {
              row.isAggregate = undefined
            }
            childList.push(row)
          })
          XEUtils.objectEach(groupMaps, (childList, groupValue) => {
            const { fullData: childFullData, treeData: childTreeData } = handleGroupData(childList, rowGroups.slice(1))
            const aggRow: VxeTableDefines.AggregateRowInfo = {
              isAggregate: true,
              aggData: {},
              groupContent: groupValue,
              groupField,
              childCount: 0,
              [rowField]: getRowUniqueId(),
              [parentField]: null,
              [childrenField]: childTreeData,
              [mapChildrenField]: childTreeData
            }
            aggRow[rowkey] = createRowId(rowOpts, aggRow, rowkey)
            if (checkField) {
              aggRow[checkField] = false
            }
            if (indeterminateField) {
              aggRow[indeterminateField] = false
            }
            aggList.push(aggRow)
            treeData.push(aggRow)
            fullData.push(aggRow)
            if (childFullData.length) {
              fullData.push(...childFullData)
            }
          })
          handleeGroupSummary(aggList)
        }
      }
      return {
        treeData,
        fullData
      }
    }

    const initData = () => {
      const { data } = props
      loadTableData(data || [], true, true).then(() => {
        if (data && data.length) {
          internalData.inited = true
          internalData.initStatus = true
          handleLoadDefaults()
        }
        handleInitDefaults()
        updateStyle()
        if (!reactData.isAllOverflow) {
          calcCellHeight()
          updateRowOffsetTop()
        }
      })
      nextTick(() => {
        dispatchEvent('ready', {}, null)
      })
    }

    /**
     * 加载表格数据
     * @param {Array} datas 数据
     */
    const loadTableData = (datas: any[], isReload: boolean, isReset: boolean) => {
      const { keepSource, treeConfig, rowGroupConfig, aggregateConfig } = props
      const { rowGroupList, scrollYLoad: oldScrollYLoad } = reactData
      const { initStatus, scrollYStore, scrollXStore, lastScrollLeft, lastScrollTop } = internalData
      const rowOpts = computeRowOpts.value
      const treeOpts = computeTreeOpts.value
      const expandOpts = computeExpandOpts.value
      const { transform } = treeOpts
      const childrenField = treeOpts.children || treeOpts.childrenField
      let treeData = []
      let fullData = reactive(datas ? datas.slice(0) : []) // 转为响应式数据
      if (fullData.length > supportMaxRow) {
        errLog('vxe.error.errMaxRow', [supportMaxRow])
      }
      if (treeConfig && rowGroupList.length) {
        errLog('vxe.error.noTree', ['aggregate-config'])
        return nextTick()
      }
      if (rowOpts.drag && rowGroupList.length) {
        errLog('vxe.error.errConflicts', ['row-config.drag', 'aggregate-config'])
        return nextTick()
      }
      let isRGroup = false
      if (treeConfig) {
        if (transform) {
          // 树结构自动转换
          if (!treeOpts.rowField) {
            errLog('vxe.error.reqProp', ['tree-config.rowField'])
          }
          if (!treeOpts.parentField) {
            errLog('vxe.error.reqProp', ['tree-config.parentField'])
          }
          if (!childrenField) {
            errLog('vxe.error.reqProp', ['tree-config.childrenField'])
          }
          if (!treeOpts.mapChildrenField) {
            errLog('vxe.error.reqProp', ['tree-config.mapChildrenField'])
          }
          if (childrenField === treeOpts.mapChildrenField) {
            errLog('vxe.error.errConflicts', ['tree-config.childrenField', 'tree-config.mapChildrenField'])
          }
          // fullData.forEach(row => {
          //   if (row[treeOpts.children] && row[treeOpts.children].length) {
          //     warnLog('vxe.error.errConflicts', ['tree-config.transform', `row.${treeOpts.children}`])
          //   }
          // })
          treeData = XEUtils.toArrayTree(fullData, {
            key: treeOpts.rowField,
            parentKey: treeOpts.parentField,
            children: childrenField,
            mapChildren: treeOpts.mapChildrenField
          })
          fullData = treeData.slice(0)
        } else {
          treeData = fullData.slice(0)
        }
      } else if ((aggregateConfig || rowGroupConfig) && rowGroupList.length) {
        const groupRest = handleGroupData(fullData, rowGroupList)
        treeData = groupRest.treeData
        fullData = groupRest.fullData
        isRGroup = true
      }
      reactData.isRowGroupStatus = isRGroup
      scrollYStore.startIndex = 0
      scrollYStore.endIndex = 1
      scrollXStore.startIndex = 0
      scrollXStore.endIndex = 1
      internalData.cvCacheMaps = {}
      reactData.isRowLoading = true
      reactData.scrollVMLoading = false
      reactData.treeExpandedFlag++
      reactData.rowExpandedFlag++
      internalData.insertRowMaps = {}
      reactData.insertRowFlag++
      internalData.removeRowMaps = {}
      reactData.removeRowFlag++
      const sYLoad = updateScrollYStatus(fullData)
      // 全量数据
      internalData.tableFullData = fullData
      internalData.tableFullTreeData = isRGroup ? [] : treeData
      internalData.tableFullGroupData = isRGroup ? treeData : []
      // 缓存数据
      $xeTable.cacheRowMap(isReset)
      // 原始数据
      internalData.tableSynchData = datas
      if (isReset) {
        internalData.isResizeCellHeight = false
      }
      // 克隆原数据，用于显示编辑状态，与编辑值做对比
      if (keepSource) {
        $xeTable.cacheSourceMap(fullData)
      }
      if ($xeTable.clearCellAreas && props.mouseConfig) {
        $xeTable.clearCellAreas()
        $xeTable.clearCopyCellArea()
      }
      $xeTable.clearMergeCells()
      $xeTable.clearMergeFooterItems()
      $xeTable.handleTableData(true)
      $xeTable.updateFooter()
      $xeTable.handleUpdateBodyMerge()
      return nextTick().then(() => {
        updateHeight()
        updateStyle()
      }).then(() => {
        computeScrollLoad()
      }).then(() => {
        const virtualYOpts = computeVirtualYOpts.value
        // 是否启用了虚拟滚动
        if (sYLoad) {
          scrollYStore.endIndex = scrollYStore.visibleSize
        }

        if (sYLoad) {
          if (reactData.expandColumn && expandOpts.mode !== 'fixed') {
            errLog('vxe.error.notConflictProp', ['column.type="expand', 'expand-config.mode="fixed"'])
          }
          if (virtualYOpts.mode === 'scroll' && reactData.expandColumn && expandOpts.mode === 'fixed') {
            warnLog('vxe.error.notConflictProp', ['virtual-y-config.mode=scroll', 'expand-config.mode=inside'])
          }
          // if (showOverflow) {
          //   if (!rowOpts.height) {
          //     const errColumn = internalData.tableFullColumn.find(column => column.showOverflow === false)
          //     if (errColumn) {
          //       errLog('vxe.error.errProp', [`column[field="${errColumn.field}"].show-overflow=false`, 'show-overflow=true'])
          //     }
          //   }
          // }

          if (!(props.height || props.maxHeight)) {
            errLog('vxe.error.reqSupportProp', ['virtual-y-config.enabled = true', 'height | max-height'])
          }
          // if (!props.showOverflow) {
          //   warnLog('vxe.error.reqProp', ['table.show-overflow'])
          // }
          // if (props.spanMethod) {
          //   warnLog('vxe.error.scrollErrProp', ['span-method'])
          // }
        }

        handleReserveStatus()
        $xeTable.checkSelectionStatus()
        $xeTable.dispatchEvent('data-change', {
          visibleColumn: internalData.visibleColumn,
          visibleData: internalData.afterFullData
        }, null)
        return new Promise<void>(resolve => {
          nextTick()
            .then(() => handleRecalculateStyle(false, false, false))
            .then(() => {
              handleRecalculateStyle(false, true, true)
              updateRowOffsetTop()
            })
            .then(() => {
              let targetScrollLeft = lastScrollLeft
              let targetScrollTop = lastScrollTop
              const virtualXOpts = computeVirtualXOpts.value
              const virtualYOpts = computeVirtualYOpts.value
              // 是否在更新数据之后自动滚动重置滚动条
              if (virtualXOpts.scrollToLeftOnChange) {
                targetScrollLeft = 0
              }
              if (virtualYOpts.scrollToTopOnChange) {
                targetScrollTop = 0
              }
              reactData.isRowLoading = false
              handleRecalculateStyle(false, false, false)
              updateTreeLineStyle()

              // 如果是自动行高，特殊情况需调用 recalculate 手动刷新
              if (!props.showOverflow) {
                setTimeout(() => {
                  handleLazyRecalculate(false, true, true)
                  setTimeout(() => handleLazyRecalculate(false, true, true), 3000)
                }, 2000)
              }
              // 是否变更虚拟滚动
              if (oldScrollYLoad === sYLoad) {
                restoreScrollLocation($xeTable, targetScrollLeft, targetScrollTop)
                  .then(() => {
                    handleRecalculateStyle(false, true, true)
                    updateRowOffsetTop()
                    updateTreeLineStyle()
                    nextTick(() => resolve())
                  })
              } else {
                setTimeout(() => {
                  restoreScrollLocation($xeTable, targetScrollLeft, targetScrollTop)
                    .then(() => {
                      handleRecalculateStyle(false, true, true)
                      updateRowOffsetTop()
                      updateTreeLineStyle()
                      nextTick(() => resolve())
                    })
                })
              }
            })
        }).then(() => {
          if (initStatus) {
            dispatchEvent('data-rendered', {
              isReload,
              visibleColumn: internalData.visibleColumn,
              visibleData: internalData.afterFullData
            }, null)
          } else {
            dispatchEvent('init-rendered', {
              visibleColumn: internalData.visibleColumn,
              visibleData: internalData.afterFullData
            }, null)
          }
        })
      })
    }

    /**
     * 处理数据加载默认行为
     * 默认执行一次，除非被重置
     */
    const handleLoadDefaults = () => {
      handleDefaultSelectionChecked()
      handleDefaultRadioChecked()
      handleDefaultRowExpand()
      handleDefaultTreeExpand()
      handleDefaultRowGroupExpand()
      handleDefaultMergeCells()
      handleDefaultMergeHeaderItems()
      handleDefaultMergeFooterItems()
      nextTick(() => {
        setTimeout(() => $xeTable.recalculate())
      })
    }

    /**
     * 处理初始化的默认行为
     * 只会执行一次
     */
    const handleInitDefaults = () => {
      handleDefaultSort()
    }

    const handleTableColumn = () => {
      const { scrollXLoad } = reactData
      const { visibleColumn, scrollXStore, fullColumnIdData } = internalData
      const tableColumn = scrollXLoad ? visibleColumn.slice(scrollXStore.startIndex, scrollXStore.endIndex) : visibleColumn.slice(0)
      tableColumn.forEach((column, $index) => {
        const colid = column.id
        const colRest = fullColumnIdData[colid]
        if (colRest) {
          colRest.$index = $index
        }
      })
      reactData.tableColumn = tableColumn
    }

    const handleUpdateColumn = () => {
      const columnList = XEUtils.orderBy(internalData.collectColumn, 'renderSortNumber')
      internalData.collectColumn = columnList
      const tFullColumn = getColumnList(columnList)
      internalData.tableFullColumn = tFullColumn
      reactData.updateColFlag++
      cacheColumnMap()
    }

    const loadScrollXData = () => {
      const { isScrollXBig } = reactData
      const { mergeBodyList, mergeFooterList, mergeBodyRowMaps, scrollXStore } = internalData
      const { preloadSize, startIndex, endIndex, offsetSize } = scrollXStore
      const { toVisibleIndex, visibleSize } = handleVirtualXVisible()
      const offsetItem: VxeTableDefines.MergeCacheCol = {
        startIndex: Math.max(0, isScrollXBig ? toVisibleIndex - 1 : toVisibleIndex - 1 - offsetSize - preloadSize),
        endIndex: isScrollXBig ? toVisibleIndex + visibleSize : toVisibleIndex + visibleSize + offsetSize + preloadSize
      }
      scrollXStore.visibleStartIndex = toVisibleIndex - 1
      scrollXStore.visibleEndIndex = toVisibleIndex + visibleSize + 1
      calculateMergerOffsetIndex(mergeBodyList.concat(mergeFooterList), mergeBodyRowMaps, offsetItem, 'col')
      const { startIndex: offsetStartIndex, endIndex: offsetEndIndex } = offsetItem
      if (toVisibleIndex <= startIndex || toVisibleIndex >= endIndex - visibleSize - 1) {
        if (startIndex !== offsetStartIndex || endIndex !== offsetEndIndex) {
          scrollXStore.startIndex = offsetStartIndex
          scrollXStore.endIndex = offsetEndIndex
          $xeTable.updateScrollXData()
        }
      }
      $xeTable.closeTooltip()
    }

    const parseColumns = (isReset: boolean) => {
      // const { showOverflow } = props
      // const rowOpts = computeRowOpts.value
      const leftList: VxeTableDefines.ColumnInfo[] = []
      const centerList: VxeTableDefines.ColumnInfo[] = []
      const rightList: VxeTableDefines.ColumnInfo[] = []
      const { isGroup, columnStore } = reactData
      const { collectColumn, tableFullColumn, scrollXStore, fullColumnIdData } = internalData
      // 如果是分组表头，如果子列全部被隐藏，则根列也隐藏
      if (isGroup) {
        const leftGroupList: VxeTableDefines.ColumnInfo[] = []
        const centerGroupList: VxeTableDefines.ColumnInfo[] = []
        const rightGroupList: VxeTableDefines.ColumnInfo[] = []
        XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
          const isColGroup = hasChildrenList(column)
          // 如果是分组，必须按组设置固定列，不允许给子列设置固定
          if (parentColumn && parentColumn.fixed) {
            column.fixed = parentColumn.fixed
          }
          if (parentColumn && (column.fixed || '') !== (parentColumn.fixed || '')) {
            errLog('vxe.error.groupFixed')
          }
          if (isColGroup) {
            column.visible = !!XEUtils.findTree(column.children, (subColumn) => hasChildrenList(subColumn) ? false : subColumn.visible)
          } else if (column.visible) {
            if (column.fixed === 'left') {
              leftList.push(column)
            } else if (column.fixed === 'right') {
              rightList.push(column)
            } else {
              centerList.push(column)
            }
          }
        })
        collectColumn.forEach((column) => {
          if (column.visible) {
            if (column.fixed === 'left') {
              leftGroupList.push(column)
            } else if (column.fixed === 'right') {
              rightGroupList.push(column)
            } else {
              centerGroupList.push(column)
            }
          }
        })
        reactData.tableGroupColumn = leftGroupList.concat(centerGroupList).concat(rightGroupList)
      } else {
        // 重新分配列
        tableFullColumn.forEach((column) => {
          if (column.visible) {
            if (column.fixed === 'left') {
              leftList.push(column)
            } else if (column.fixed === 'right') {
              rightList.push(column)
            } else {
              centerList.push(column)
            }
          }
        })
      }
      const visibleColumn = leftList.concat(centerList).concat(rightList)
      internalData.visibleColumn = visibleColumn
      updateColumnOffsetLeft()
      const sXLoad = updateScrollXStatus()
      reactData.hasFixedColumn = leftList.length > 0 || rightList.length > 0
      Object.assign(columnStore, { leftList, centerList, rightList })
      if (sXLoad) {
        // if (showOverflow) {
        //   if (!rowOpts.height) {
        //     const errColumn = internalData.tableFullColumn.find(column => column.showOverflow === false)
        //     if (errColumn) {
        //       errLog('vxe.error.errProp', [`column[field="${errColumn.field}"].show-overflow=false`, 'show-overflow=true'])
        //     }
        //   }
        // }
        // if (props.showHeader && !props.showHeaderOverflow) {
        //   warnLog('vxe.error.reqProp', ['show-header-overflow'])
        // }
        // if (props.showFooter && !props.showFooterOverflow) {
        //   warnLog('vxe.error.reqProp', ['show-footer-overflow'])
        // }
        if (props.spanMethod) {
          warnLog('vxe.error.scrollErrProp', ['span-method'])
        }
        if (props.footerSpanMethod) {
          warnLog('vxe.error.scrollErrProp', ['footer-span-method'])
        }
        if (isReset) {
          const { visibleSize } = handleVirtualXVisible()
          scrollXStore.startIndex = 0
          scrollXStore.endIndex = visibleSize
          scrollXStore.visibleSize = visibleSize
          scrollXStore.visibleStartIndex = 0
          scrollXStore.visibleEndIndex = visibleSize
        }
      }
      // 如果列被显示/隐藏，则清除合并状态
      // 如果列被设置为固定，则清除合并状态
      if (visibleColumn.length !== internalData.visibleColumn.length || !internalData.visibleColumn.every((column, index) => column === visibleColumn[index])) {
        $xeTable.clearMergeCells()
        $xeTable.clearMergeFooterItems()
      }
      visibleColumn.forEach((column, index) => {
        const colid = column.id
        const colRest = fullColumnIdData[colid]
        if (colRest) {
          colRest._index = index
        }
      })
      handleTableColumn()
      handleUpdateAggValues()
      if (isReset) {
        updateColumnOffsetLeft()
        return $xeTable.updateFooter().then(() => {
          return $xeTable.recalculate()
        }).then(() => {
          $xeTable.updateCellAreas()
          return $xeTable.recalculate()
        })
      }
      return $xeTable.updateFooter()
    }

    const initColumnHierarchy = () => {
      const { collectColumn } = internalData
      const fullColIdData: Record<string, VxeTableDefines.ColumnCacheItem> = {}
      const fullColFieldData: Record<string, VxeTableDefines.ColumnCacheItem> = {}
      let sortIndex = 1
      XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
        const { id: colid, field } = column
        const parentId = parentColumn ? parentColumn.id : null
        const rest = { $index: -1, _index: -1, column, colid, index, items, parent: parentColumn || null, width: 0, oLeft: 0 }
        column.parentId = parentId
        column.defaultParentId = parentId
        column.sortNumber = sortIndex
        column.renderSortNumber = sortIndex
        sortIndex++
        if (field) {
          if (fullColFieldData[field]) {
            errLog('vxe.error.colRepet', ['field', field])
          }
          fullColFieldData[field] = rest
        }
        fullColIdData[colid] = rest
      })
      internalData.fullColumnIdData = fullColIdData
      internalData.fullColumnFieldData = fullColFieldData
    }

    const handleInitColumn = (collectColumn: VxeTableDefines.ColumnInfo[]) => {
      const expandOpts = computeExpandOpts.value
      internalData.collectColumn = collectColumn
      const tFullColumn = getColumnList(collectColumn)
      internalData.tableFullColumn = tFullColumn
      reactData.updateColFlag++
      reactData.isColLoading = true
      initColumnHierarchy()
      return Promise.resolve(
        restoreCustomStorage()
      ).then(() => {
        const { scrollXLoad, scrollYLoad, expandColumn } = reactData
        cacheColumnMap()
        parseColumns(true).then(() => {
          if (reactData.scrollXLoad) {
            loadScrollXData()
          }
        })
        $xeTable.clearMergeCells()
        $xeTable.clearMergeFooterItems()
        $xeTable.handleTableData(true)
        $xeTable.handleAggregateSummaryData()

        if ((scrollXLoad || scrollYLoad) && (expandColumn && expandOpts.mode !== 'fixed')) {
          warnLog('vxe.error.scrollErrProp', ['column.type=expand'])
        }

        return nextTick().then(() => {
          if ($xeToolbar) {
            $xeToolbar.syncUpdate({
              collectColumn: internalData.collectColumn,
              $table: $xeTable
            })
          }
          if ($xeTable.handleUpdateCustomColumn) {
            $xeTable.handleUpdateCustomColumn()
          }
          const columnOpts = computeColumnOpts.value
          if (props.showCustomHeader && reactData.isGroup && (columnOpts.resizable || props.resizable)) {
            warnLog('vxe.error.notConflictProp', ['show-custom-header & colgroup', 'column-config.resizable=false'])
          }
          reactData.isColLoading = false
          return handleLazyRecalculate(false, true, true)
        })
      })
    }

    const updateScrollXStatus = (fullColumn?: any[]) => {
      const virtualXOpts = computeVirtualXOpts.value
      const allCols = fullColumn || internalData.tableFullColumn
      // 如果gt为0，则总是启用
      const scrollXLoad = !!virtualXOpts.enabled && virtualXOpts.gt > -1 && (virtualXOpts.gt === 0 || virtualXOpts.gt < allCols.length)
      reactData.scrollXLoad = scrollXLoad
      return scrollXLoad
    }

    const updateScrollYStatus = (fullData?: any[]) => {
      const { treeConfig } = props
      const $xeGanttView = internalData.xeGanttView
      const virtualYOpts = computeVirtualYOpts.value
      const treeOpts = computeTreeOpts.value
      const { transform } = treeOpts
      const allList = fullData || internalData.tableFullData
      // 如果gt为0，则总是启用
      const scrollYLoad = (transform || !treeConfig) && !!virtualYOpts.enabled && virtualYOpts.gt > -1 && (virtualYOpts.gt === 0 || virtualYOpts.gt < allList.length)
      reactData.scrollYLoad = scrollYLoad
      if ($xeGanttView && $xeGanttView.handleUpdateSYStatus) {
        $xeGanttView.handleUpdateSYStatus(scrollYLoad)
      }
      return scrollYLoad
    }

    /**
     * 展开与收起树节点
     * @param rows
     * @param expanded
     * @returns
     */
    const handleBaseTreeExpand = (rows: any[], expanded: boolean) => {
      const { treeNodeColumn } = reactData
      const { fullAllDataRowIdData, tableFullTreeData, treeExpandedMaps, treeExpandLazyLoadedMaps } = internalData
      const treeOpts = computeTreeOpts.value
      const { reserve, lazy, accordion, toggleMethod } = treeOpts
      const childrenField = treeOpts.children || treeOpts.childrenField
      const hasChildField = treeOpts.hasChild || treeOpts.hasChildField
      const result: any[] = []
      const columnIndex = $xeTable.getColumnIndex(treeNodeColumn)
      const $columnIndex = $xeTable.getVMColumnIndex(treeNodeColumn)
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      let validRows = toggleMethod ? rows.filter((row: any) => toggleMethod({ $table: $xeTable, expanded, column: treeNodeColumn, columnIndex, $columnIndex, row })) : rows
      if (accordion) {
        validRows = validRows.length ? [validRows[validRows.length - 1]] : []
        // 同一级只能展开一个
        const matchObj = XEUtils.findTree(tableFullTreeData, item => item === validRows[0], { children: childrenField })
        if (matchObj) {
          matchObj.items.forEach(item => {
            const rowid = handleGetRowId(item)
            if (treeExpandedMaps[rowid]) {
              delete treeExpandedMaps[rowid]
            }
          })
        }
      }
      if (expanded) {
        validRows.forEach((row: any) => {
          const rowid = handleGetRowId(row)
          if (!treeExpandedMaps[rowid]) {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              const isLoad = lazy && row[hasChildField] && !rowRest.treeLoaded && !treeExpandLazyLoadedMaps[rowid]
              // 是否使用懒加载
              if (isLoad) {
                result.push(handleAsyncTreeExpandChilds(row))
              } else {
                if (row[childrenField] && row[childrenField].length) {
                  treeExpandedMaps[rowid] = row
                }
              }
            }
          }
        })
      } else {
        validRows.forEach(item => {
          const rowid = handleGetRowId(item)
          if (treeExpandedMaps[rowid]) {
            delete treeExpandedMaps[rowid]
          }
        })
      }
      if (reserve) {
        validRows.forEach((row: any) => handleTreeExpandReserve(row, expanded))
      }
      reactData.treeExpandedFlag++
      return Promise.all(result).then(() => {
        updateTreeLineStyle()
        return $xeTable.recalculate()
      })
    }

    /**
     * 虚拟树的展开与收起
     * @param rows
     * @param expanded
     * @returns
     */
    const handleVirtualTreeExpand = (rows: any[], expanded: boolean) => {
      const { lastScrollLeft, lastScrollTop } = internalData
      return handleBaseTreeExpand(rows, expanded).then(() => {
        handleVirtualTreeToList()
        $xeTable.handleTableData()
        reactData.treeExpandedFlag++
        updateAfterDataIndex()
        return nextTick()
      }).then(() => {
        return handleRecalculateStyle(true, true, true)
      }).then(() => {
        restoreScrollLocation($xeTable, lastScrollLeft, lastScrollTop)
        updateTreeLineStyle()
        setTimeout(() => {
          $xeTable.updateCellAreas()
        }, 30)
      })
    }

    /**
     * 展开与收起行分组节点
     * @param rows
     * @param expanded
     * @returns
     */
    const handleRowGroupBaseExpand = (rows: any[], expanded: boolean) => {
      const { fullAllDataRowIdData, tableFullGroupData, rowGroupExpandedMaps } = internalData
      const aggregateOpts = computeAggregateOpts.value
      const { mapChildrenField, accordion } = aggregateOpts
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      let validRows = rows
      if (mapChildrenField) {
        if (accordion) {
          validRows = validRows.length ? [validRows[validRows.length - 1]] : []
          // 同一级只能展开一个
          const matchObj = XEUtils.findTree(tableFullGroupData, item => getRowid($xeTable, item) === getRowid($xeTable, validRows[0]), { children: mapChildrenField })
          if (matchObj) {
            matchObj.items.forEach(item => {
              const rowid = handleGetRowId(item)
              if (rowGroupExpandedMaps[rowid]) {
                delete rowGroupExpandedMaps[rowid]
              }
            })
          }
        }
        if (expanded) {
          validRows.forEach((row) => {
            const rowid = handleGetRowId(row)
            if (!rowGroupExpandedMaps[rowid]) {
              const rowRest = fullAllDataRowIdData[rowid]
              if (rowRest) {
                if (row[mapChildrenField] && row[mapChildrenField].length) {
                  rowGroupExpandedMaps[rowid] = row
                }
              }
            }
          })
        } else {
          validRows.forEach(item => {
            const rowid = handleGetRowId(item)
            if (rowGroupExpandedMaps[rowid]) {
              delete rowGroupExpandedMaps[rowid]
            }
          })
        }
      }
      reactData.rowGroupExpandedFlag++
      return $xeTable.recalculate()
    }

    /**
     * 行分组的展开与收起
     * @param rows
     * @param expanded
     * @returns
     */
    const handleRowGroupVirtualExpand = (rows: any[], expanded: boolean) => {
      return handleRowGroupBaseExpand(rows, expanded).then(() => {
        handleVirtualTreeToList()
        $xeTable.handleTableData()
        reactData.rowGroupExpandedFlag++
        updateAfterDataIndex()
        return nextTick()
      }).then(() => {
        return handleLazyRecalculate(true, true, true)
      }).then(() => {
        setTimeout(() => {
          $xeTable.updateCellAreas()
        }, 30)
      })
    }

    /**
     * 处理默认展开分组行
     */
    const handleDefaultRowGroupExpand = () => {
      const { isRowGroupStatus } = reactData
      if (isRowGroupStatus) {
        const aggregateOpts = computeAggregateOpts.value
        const { expandAll, expandGroupFields } = aggregateOpts
        if (expandAll) {
          $xeTable.setAllRowGroupExpand(true)
        } else if (expandGroupFields && expandGroupFields.length) {
          $xeTable.setRowGroupExpandByField(expandGroupFields, true)
        }
      }
    }

    const handleCheckAllEvent = (evnt: Event | null, value: any) => {
      handleCheckedAllCheckboxRow(value)
      if (evnt) {
        dispatchEvent('checkbox-all', {
          records: () => $xeTable.getCheckboxRecords(),
          reserves: () => $xeTable.getCheckboxReserveRecords(),
          indeterminates: () => $xeTable.getCheckboxIndeterminateRecords(),
          checked: value
        }, evnt)
      }
    }

    /**
     * 纵向 Y 可视渲染处理
     */
    const loadScrollYData = () => {
      const { isAllOverflow, isScrollYBig } = reactData
      const { mergeBodyList, mergeBodyColMaps, scrollYStore } = internalData
      const { preloadSize, startIndex, endIndex, offsetSize } = scrollYStore
      const autoOffsetYSize = isAllOverflow ? offsetSize : offsetSize + 1
      const { toVisibleIndex, visibleSize } = handleVirtualYVisible()
      const offsetItem: VxeTableDefines.MergeCacheRow = {
        startIndex: Math.max(0, isScrollYBig ? toVisibleIndex - 1 : toVisibleIndex - 1 - offsetSize - preloadSize),
        endIndex: isScrollYBig ? (toVisibleIndex + visibleSize) : (toVisibleIndex + visibleSize + autoOffsetYSize + preloadSize)
      }
      scrollYStore.visibleStartIndex = toVisibleIndex - 1
      scrollYStore.visibleEndIndex = toVisibleIndex + visibleSize + 1
      calculateMergerOffsetIndex(mergeBodyList, mergeBodyColMaps, offsetItem, 'row')
      const { startIndex: offsetStartIndex, endIndex: offsetEndIndex } = offsetItem
      if (toVisibleIndex <= startIndex || toVisibleIndex >= endIndex - visibleSize - 1) {
        if (startIndex !== offsetStartIndex || endIndex !== offsetEndIndex) {
          scrollYStore.startIndex = offsetStartIndex
          scrollYStore.endIndex = offsetEndIndex
          $xeTable.updateScrollYData()
        }
      }
    }

    const createGetRowCacheProp = (prop: 'seq' | 'index' | '_index' | '$index') => {
      return function (row: any) {
        const { fullAllDataRowIdData } = internalData
        if (row) {
          const rowid = getRowid($xeTable, row)
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            return rowRest[prop]
          }
        }
        return -1
      }
    }

    const createGetColumnCacheProp = (prop: 'index' | '_index' | '$index') => {
      return function (column: VxeTableDefines.ColumnInfo) {
        const { fullColumnIdData } = internalData
        if (column) {
          const colRest = fullColumnIdData[column.id]
          if (colRest) {
            return colRest[prop]
          }
        }
        return -1
      }
    }

    const lazyScrollXData = () => {
      const { lxTimeout, lxRunTime, scrollXStore } = internalData
      const { visibleSize } = scrollXStore
      const fpsTime = visibleSize > 26 ? 26 : (visibleSize > 16 ? 14 : 6)
      if (lxTimeout) {
        clearTimeout(lxTimeout)
      }
      if (!lxRunTime || lxRunTime + fpsTime < Date.now()) {
        internalData.lxRunTime = Date.now()
        loadScrollXData()
      }
      internalData.lxTimeout = setTimeout(() => {
        internalData.lxTimeout = undefined
        internalData.lxRunTime = undefined
        loadScrollXData()
      }, fpsTime)
    }

    const lazyScrollYData = () => {
      const { lyTimeout, lyRunTime, scrollYStore } = internalData
      const { visibleSize } = scrollYStore
      const fpsTime = visibleSize > 30 ? 32 : (visibleSize > 20 ? 18 : 8)
      if (lyTimeout) {
        clearTimeout(lyTimeout)
      }
      if (!lyRunTime || lyRunTime + fpsTime < Date.now()) {
        internalData.lyRunTime = Date.now()
        loadScrollYData()
      }
      internalData.lyTimeout = setTimeout(() => {
        internalData.lyTimeout = undefined
        internalData.lyRunTime = undefined
        loadScrollYData()
      }, fpsTime)
    }

    const handleSyncScroll = (isRollX: boolean, isRollY: boolean) => {
      const { scrollXLoad, scrollYLoad, isAllOverflow } = reactData
      internalData.lcsRunTime = Date.now()
      internalData.lcsTimeout = undefined
      internalData.intoRunScroll = false
      internalData.inVirtualScroll = false
      internalData.inWheelScroll = false
      internalData.inHeaderScroll = false
      internalData.inBodyScroll = false
      internalData.inFooterScroll = false
      reactData.lazScrollLoading = false
      internalData.scrollRenderType = ''

      let xRest = null
      let yRest = null
      if (!isAllOverflow) {
        calcCellHeight()
        updateRowOffsetTop()
      }
      if (isRollX && scrollXLoad) {
        xRest = $xeTable.updateScrollXData()
      }
      if (isRollY && scrollYLoad) {
        yRest = $xeTable.updateScrollYData().then(() => {
          if (!isAllOverflow) {
            calcCellHeight()
            updateRowOffsetTop()
          }
          return $xeTable.updateScrollYSpace()
        })
      }
      updateRowExpandStyle()
      return Promise.all([
        xRest,
        yRest,
        scrollXLoad || scrollYLoad ? $xeTable.updateCellAreas() : null
      ])
    }

    const checkLastSyncScroll = (isRollX: boolean, isRollY: boolean) => {
      const { lcsTimeout } = internalData
      reactData.lazScrollLoading = true
      if (lcsTimeout) {
        clearTimeout(lcsTimeout)
      }
      internalData.lcsTimeout = setTimeout(() => {
        handleSyncScroll(isRollX, isRollY).then(() => {
          if (reactData.scrollXLoad || reactData.scrollYLoad) {
            nextTick(() => {
              updateRowExpandStyle()
              if (!internalData.lcsTimeout) {
                handleSyncScroll(isRollX, isRollY)
              }
            })
          }
        })
      }, 200)
    }

    const getWheelSpeed = (lastScrollTime: number) => {
      let multiple = 1
      const currTime = Date.now()
      if (lastScrollTime + 25 > currTime) {
        multiple = 1.18
      } else if (lastScrollTime + 30 > currTime) {
        multiple = 1.15
      } else if (lastScrollTime + 40 > currTime) {
        multiple = 1.12
      } else if (lastScrollTime + 55 > currTime) {
        multiple = 1.09
      } else if (lastScrollTime + 75 > currTime) {
        multiple = 1.06
      } else if (lastScrollTime + 100 > currTime) {
        multiple = 1.03
      }
      return multiple
    }

    const syncGanttScrollTop = (scrollTop: number) => {
      const $xeGanttView = internalData.xeGanttView
      if ($xeGanttView) {
        const ganttInternalData = $xeGanttView.internalData
        const { elemStore: ganttElemStore } = ganttInternalData
        const ganttBodyScrollElem = getRefElem(ganttElemStore['main-body-scroll'])
        if (ganttBodyScrollElem) {
          ganttBodyScrollElem.scrollTop = scrollTop
        }
      }
    }

    const dispatchEvent = (type: ValueOf<VxeTableEmits>, params: Record<string, any>, evnt: Event | null) => {
      emit(type, createEvent(evnt, { $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }, params))
    }

    const handleScrollToRowColumn = (fieldOrColumn: string | VxeTableDefines.ColumnInfo | null, row: any, options?: VxeTableDefines.ScrollToRowConfig) => {
      const { fullColumnIdData } = internalData
      const column = handleFieldOrColumn($xeTable, fieldOrColumn)
      if (column && fullColumnIdData[column.id]) {
        return colToVisible($xeTable, options ? options.colAlign !== false : true, column, row)
      }
      return nextTick()
    }

    const handleUpdateResize = () => {
      const el = refElem.value
      if (el && el.clientWidth && el.clientHeight) {
        $xeTable.recalculate()
      }
    }

    const handleUpdateColResize = (evnt: MouseEvent, params: any) => {
      $xeTable.analyColumnWidth()
      $xeTable.recalculate().then(() => {
        $xeTable.saveCustomStore('update:width')
        $xeTable.refreshScroll().then(() => {
          $xeTable.updateCellAreas()
        })
        $xeTable.dispatchEvent('column-resizable-change', params, evnt)
        // 已废弃 resizable-change
        $xeTable.dispatchEvent('resizable-change', params, evnt)
        setTimeout(() => {
          $xeTable.recalculate(true)
        }, 300)
      })
    }

    const handleUpdateRowResize = (evnt: MouseEvent, params: any) => {
      reactData.resizeHeightFlag++
      $xeTable.recalculate().then(() => {
        $xeTable.refreshScroll().then(() => {
          $xeTable.updateCellAreas()
        })
        $xeTable.dispatchEvent('row-resizable-change', params, evnt)
        setTimeout(() => {
          $xeTable.recalculate(true)
        }, 300)
      })
    }

    const updateColumnOffsetLeft = () => {
      const { visibleColumn, fullColumnIdData } = internalData
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      let offsetLeft = 0
      for (let cIndex = 0, rLen = visibleColumn.length; cIndex < rLen; cIndex++) {
        const column = visibleColumn[cIndex]
        const colid = column.id
        const colRest = fullColumnIdData[colid]
        if (colRest) {
          colRest.oLeft = offsetLeft
        }
        offsetLeft += column.renderWidth
      }
    }

    const updateRowOffsetTop = () => {
      const { expandColumn } = reactData
      const { afterFullData, fullAllDataRowIdData, rowExpandedMaps } = internalData
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      const expandOpts = computeExpandOpts.value
      const rowOpts = computeRowOpts.value
      const cellOpts = computeCellOpts.value
      const defaultRowHeight = computeDefaultRowHeight.value
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      let offsetTop = 0
      for (let rIndex = 0, rLen = afterFullData.length; rIndex < rLen; rIndex++) {
        const row = afterFullData[rIndex]
        const rowid = handleGetRowId(row)
        const rowRest = fullAllDataRowIdData[rowid] || {}
        rowRest.oTop = offsetTop
        offsetTop += rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight
        // 是否展开行
        if (expandColumn && rowExpandedMaps[rowid]) {
          offsetTop += rowRest.expandHeight || expandOpts.height || 0
        }
      }
    }

    /**
     * 更新展开行样式
     */
    const updateRowExpandStyle = () => {
      const { expandColumn, scrollYLoad, scrollYTop, isScrollYBig } = reactData
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      const expandOpts = computeExpandOpts.value
      const rowOpts = computeRowOpts.value
      const cellOpts = computeCellOpts.value
      const defaultRowHeight = computeDefaultRowHeight.value
      const { mode } = expandOpts
      if (expandColumn && mode === 'fixed') {
        const { elemStore, fullAllDataRowIdData } = internalData
        const rowExpandEl = refRowExpandElem.value
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        if (rowExpandEl && bodyScrollElem) {
          let isUpdateHeight = false
          XEUtils.arrayEach(rowExpandEl.children, reEl => {
            const expandEl = reEl as HTMLDivElement
            const rowid = expandEl.getAttribute('rowid') || ''
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              const expandHeight = expandEl.offsetHeight
              const trEl = bodyScrollElem.querySelector(`.vxe-body--row[rowid="${rowid}"]`) as HTMLTableCellElement
              let offsetTop = 0
              if (scrollYLoad) {
                if (isScrollYBig && trEl) {
                  offsetTop = trEl.offsetTop + trEl.offsetHeight
                } else {
                  offsetTop = rowRest.oTop + (rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight)
                }
              } else {
                if (trEl) {
                  offsetTop = trEl.offsetTop + trEl.offsetHeight
                }
              }
              if (isScrollYBig) {
                offsetTop += scrollYTop
              }
              expandEl.style.top = toCssUnit(offsetTop)

              if (!isUpdateHeight) {
                if (rowRest.expandHeight !== expandHeight) {
                  isUpdateHeight = true
                }
              }
              rowRest.expandHeight = expandHeight
            }
          })
          if (isUpdateHeight) {
            reactData.rowExpandHeightFlag++
            nextTick(() => {
              updateRowOffsetTop()
            })
          }
        }
      }
    }

    /**
     * 更新树连接线样式
     */
    const updateTreeLineStyle = () => {
      const { treeConfig } = props
      if (!treeConfig) {
        return
      }
      const { tableData } = reactData
      const { fullAllDataRowIdData, treeExpandedMaps } = internalData
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      const cellOpts = computeCellOpts.value
      const rowOpts = computeRowOpts.value
      const defaultRowHeight = computeDefaultRowHeight.value
      const treeOpts = computeTreeOpts.value
      const { transform, mapChildrenField } = treeOpts
      const childrenField = treeOpts.children || treeOpts.childrenField
      const { handleGetRowId } = createHandleGetRowId($xeTable)
      const expParentList: {
        row: any
        prevRow: any
        nextRow: any
      }[] = []

      const handleNodeRow = (row: any, rIndex: number, rows: any[]) => {
        const rowid = handleGetRowId(row)
        const rowRest = fullAllDataRowIdData[rowid] || {}
        const childList: any[] = row[transform ? mapChildrenField : childrenField]
        const prevRow = rows[rIndex - 1] || null
        const nextRow = rows[rIndex + 1] || null
        if (childList && childList.length && treeExpandedMaps[rowid]) {
          expParentList.push({ row, prevRow, nextRow })
          childList.forEach((childRow, crIndex) => {
            const childRowid = handleGetRowId(childRow)
            if (treeExpandedMaps[childRowid]) {
              handleNodeRow(childRow, crIndex, childList)
            }
          })
        } else {
          if (nextRow) {
            const nextRowid = handleGetRowId(nextRow)
            const nextRowRest = fullAllDataRowIdData[nextRowid] || {}
            const currCellHeight = getCellRestHeight(rowRest, cellOpts, rowOpts, defaultRowHeight)
            const nextCellHeight = getCellRestHeight(nextRowRest, cellOpts, rowOpts, defaultRowHeight)
            rowRest.oHeight = currCellHeight
            rowRest.lineHeight = Math.floor(currCellHeight / 2 + nextCellHeight / 2)
          } else {
            rowRest.oHeight = 0
            rowRest.lineHeight = 0
          }
        }
      }
      tableData.forEach((row, rIndex) => {
        handleNodeRow(row, rIndex, tableData)
      })

      XEUtils.lastArrayEach(expParentList, ({ row, nextRow }) => {
        const rowid = handleGetRowId(row)
        const childList: any[] = row[transform ? mapChildrenField : childrenField]
        const rowRest = fullAllDataRowIdData[rowid]
        if (rowRest) {
          const currCellHeight = getCellRestHeight(rowRest, cellOpts, rowOpts, defaultRowHeight)
          let countOffsetHeight = currCellHeight
          let countLineHeight = 0
          childList.forEach((childRow) => {
            const childRowid = handleGetRowId(childRow)
            const childRowRest = fullAllDataRowIdData[childRowid] || {}
            const childList: any[] = childRow[transform ? mapChildrenField : childrenField]
            if (treeExpandedMaps[childRowid] && childList && childList.length) {
              countOffsetHeight += (childRowRest.oHeight || 0)
              countLineHeight += (childRowRest.oHeight || 0)
            } else {
              const cellHeight = getCellRestHeight(childRowRest, cellOpts, rowOpts, defaultRowHeight)
              childRowRest.oHeight = cellHeight
              childRowRest.lineHeight = cellHeight
              countOffsetHeight += cellHeight
              countLineHeight += cellHeight
            }
          })
          if (nextRow) {
            const nextRowid = handleGetRowId(nextRow)
            const nextRowRest = fullAllDataRowIdData[nextRowid] || {}
            const currCellHeight = getCellRestHeight(rowRest, cellOpts, rowOpts, defaultRowHeight)
            const nextCellHeight = getCellRestHeight(nextRowRest, cellOpts, rowOpts, defaultRowHeight)
            countOffsetHeight += currCellHeight
            countLineHeight += Math.floor(currCellHeight / 2 + nextCellHeight / 2)
          }
          rowRest.lineHeight = countLineHeight
          rowRest.oHeight = countOffsetHeight
        }
      })
    }

    const handleRowExpandScroll = () => {
      const { elemStore } = internalData
      const rowExpandEl = refRowExpandElem.value
      const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
      if (rowExpandEl && bodyScrollElem) {
        rowExpandEl.scrollTop = bodyScrollElem.scrollTop
      }
    }

    const handleColumnVisible = (visible: boolean) => {
      return function (fieldOrColumn: string | string[] | VxeTableDefines.ColumnInfo | VxeTableDefines.ColumnInfo[]) {
        let status = false
        const cols = XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : [fieldOrColumn]
        cols.forEach(item => {
          const column = handleFieldOrColumn($xeTable, item)
          if (column) {
            if (column.children && column.children.length) {
              XEUtils.eachTree([column], (item) => {
                item.visible = visible
                item.renderVisible = visible
              })
            } else {
              column.visible = visible
              column.renderVisible = visible
            }
            if (!status) {
              status = true
            }
          }
        })
        if (status) {
          return $xeTable.handleCustom()
        }
        return nextTick()
      }
    }

    tableMethods = {
      dispatchEvent,
      getEl () {
        return refElem.value
      },
      /**
       * 重置表格的一切数据状态
       */
      clearAll () {
        return clearTableAllStatus($xeTable)
      },
      /**
       * 同步 data 数据（即将废弃）
       * 如果用了该方法，那么组件将不再记录增删改的状态，只能自行实现对应逻辑
       * 对于某些特殊的场景，比如深层树节点元素发生变动时可能会用到
       */
      syncData () {
        errLog('vxe.error.delFunc', ['syncData', 'getData'])
        return nextTick().then(() => {
          reactData.tableData = []
          emit('update:data', internalData.tableFullData)
          return nextTick()
        })
      },
      /**
       * 手动处理数据，用于手动排序与筛选
       * 对于手动更改了排序、筛选...等条件后需要重新处理数据时可能会用到
       */
      updateData () {
        const { scrollXLoad, scrollYLoad } = reactData
        return $xeTable.handleTableData(true).then(() => {
          $xeTable.updateFooter()
          if (scrollXLoad || scrollYLoad) {
            if (scrollXLoad) {
              $xeTable.updateScrollXSpace()
            }
            if (scrollYLoad) {
              $xeTable.updateScrollYSpace()
            }
            return $xeTable.refreshScroll()
          }
        }).then(() => {
          $xeTable.updateCellAreas()
          return handleLazyRecalculate(true, true, true)
        }).then(() => {
          // 存在滚动行为未结束情况
          setTimeout(() => handleLazyRecalculate(false, true, true), 50)
        })
      },
      /**
       * 重新加载数据，不会清空表格状态
       * @param {Array} datas 数据
       */
      loadData (datas) {
        const { initStatus } = internalData
        return loadTableData(datas, false, false).then(() => {
          internalData.inited = true
          internalData.initStatus = true
          if (!initStatus) {
            handleLoadDefaults()
          }
          return handleLazyRecalculate(false, true, true)
        })
      },
      /**
       * 重新加载数据，会清空表格状态
       * @param {Array} datas 数据
       */
      reloadData (datas) {
        return $xeTable.clearAll()
          .then(() => {
            internalData.inited = true
            internalData.initStatus = true
            return loadTableData(datas, true, true)
          }).then(() => {
            handleLoadDefaults()
            return handleLazyRecalculate(false, true, true)
          })
      },
      /**
       * 修改行数据
       */
      setRow (rows, record) {
        if (rows && record) {
          let rest: any[] = rows
          if (!XEUtils.isArray(rows)) {
            rest = [rows]
          }
          const rowkey = getRowkey($xeTable)
          rest.forEach(row => {
            const rowid = getRowid($xeTable, row)
            const newRecord = XEUtils.clone(Object.assign({}, record), true)
            XEUtils.set(newRecord, rowkey, rowid)
            Object.assign(row, newRecord)
          })
        }
        return nextTick()
      },
      /**
       * 局部加载行数据并恢复到初始状态
       * 对于行数据需要局部更改的场景中可能会用到
       * @param {Row} row 行对象
       * @param {Object} record 新数据
       * @param {String} field 字段名
       */
      reloadRow (row, record, field?: string) {
        const { keepSource } = props
        const { tableData } = reactData
        const { sourceDataRowIdData } = internalData
        if (keepSource) {
          if ($xeTable.isAggregateRecord(row)) {
            return nextTick()
          }
          const rowkey = getRowkey($xeTable)
          const rowid = XEUtils.get(row, rowkey)
          const oRow = sourceDataRowIdData[rowid]
          if (oRow && row) {
            if (field) {
              const newValue = XEUtils.clone(XEUtils.get(record || row, field), true)
              XEUtils.set(row, field, newValue)
              XEUtils.set(oRow, field, newValue)
            } else {
              const newRecord = XEUtils.clone(Object.assign({}, record), true)
              XEUtils.set(newRecord, rowkey, rowid)
              XEUtils.destructuring(oRow, Object.assign(row, newRecord))
            }
          }
          reactData.tableData = tableData.slice(0)
        } else {
          errLog('vxe.error.reqProp', ['keep-source'])
        }
        return nextTick()
      },
      getParams () {
        return props.params
      },
      /**
       * 用于树结构，给行数据加载子节点
       */
      loadTreeChildren (row, childRecords) {
        const { keepSource } = props
        const { tableSourceData, fullDataRowIdData, fullAllDataRowIdData, sourceDataRowIdData } = internalData
        const treeOpts = computeTreeOpts.value
        const { transform, mapChildrenField } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        const parentRest = fullAllDataRowIdData[getRowid($xeTable, row)]
        const parentLevel = parentRest ? parentRest.level : 0
        return $xeTable.createData(childRecords).then((rows) => {
          if (keepSource) {
            const rowid = getRowid($xeTable, row)
            const matchObj = XEUtils.findTree(tableSourceData, (item) => rowid === getRowid($xeTable, item), { children: childrenField })
            if (matchObj) {
              matchObj.item[childrenField] = XEUtils.clone(rows, true)
            }
            rows.forEach(childRow => {
              const rowid = getRowid($xeTable, childRow)
              sourceDataRowIdData[rowid] = XEUtils.clone(childRow, true)
            })
          }
          XEUtils.eachTree(rows, (childRow, index, items, path, parentItem, nodes) => {
            const rowid = getRowid($xeTable, childRow)
            const parentRow = parentItem || parentRest.row
            const rest = { row: childRow, rowid, seq: -1, index, _index: -1, $index: -1, treeIndex: -1, _tIndex: -1, items, parent: parentRow, level: parentLevel + nodes.length, height: 0, resizeHeight: 0, oTop: 0, expandHeight: 0 }
            fullDataRowIdData[rowid] = rest
            fullAllDataRowIdData[rowid] = rest
          }, { children: childrenField })
          row[childrenField] = rows
          if (transform) {
            row[mapChildrenField] = XEUtils.clone(rows, false)
          }
          updateAfterDataIndex()
          return rows
        })
      },
      /**
       * 加载列配置
       * 对于表格列需要重载、局部递增场景下可能会用到
       * @param {ColumnInfo} columns 列配置
       */
      loadColumn (columns) {
        const { lastScrollLeft, lastScrollTop } = internalData
        const collectColumn = XEUtils.mapTree(columns, column => reactive(Cell.createColumn($xeTable, column)))
        return handleInitColumn(collectColumn).then(() => {
          let targetScrollLeft = lastScrollLeft
          let targetScrollTop = lastScrollTop
          const virtualXOpts = computeVirtualXOpts.value
          const virtualYOpts = computeVirtualYOpts.value
          // 是否在更新数据之后自动滚动重置滚动条
          if (virtualXOpts.scrollToLeftOnChange) {
            targetScrollLeft = 0
          }
          if (virtualYOpts.scrollToTopOnChange) {
            targetScrollTop = 0
          }
          restoreScrollLocation($xeTable, targetScrollLeft, targetScrollTop)
        })
      },
      /**
       * 加载列配置并恢复到初始状态
       * 对于表格列需要重载、局部递增场景下可能会用到
       * @param {ColumnInfo} columns 列配置
       */
      reloadColumn (columns) {
        return $xeTable.clearAll().then(() => {
          return $xeTable.loadColumn(columns)
        })
      },
      /**
       * 根据 tr 元素获取对应的 row 信息
       * @param {Element} tr 元素
       */
      getRowNode (tr) {
        if (tr) {
          const { fullAllDataRowIdData } = internalData
          const rowid = tr.getAttribute('rowid')
          if (rowid) {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              return {
                rowid: rowRest.rowid,
                item: rowRest.row,
                index: rowRest.index,
                items: rowRest.items,
                parent: rowRest.parent
              }
            }
          }
        }
        return null
      },
      /**
       * 根据 th/td 元素获取对应的 column 信息
       * @param {Element} cell 元素
       */
      getColumnNode (cell) {
        if (cell) {
          const { fullColumnIdData } = internalData
          const colid = cell.getAttribute('colid')
          if (colid) {
            const colRest = fullColumnIdData[colid]
            if (colRest) {
              return {
                colid: colRest.colid,
                item: colRest.column,
                index: colRest.index,
                items: colRest.items,
                parent: colRest.parent
              }
            }
          }
        }
        return null
      },
      /**
       * 根据 row 获取序号
       * @param {Row} row 行对象
       */
      getRowSeq: createGetRowCacheProp('seq'),
      /**
       * 根据 row 获取相对于 data 中的索引
       * @param {Row} row 行对象
       */
      getRowIndex: createGetRowCacheProp('index') as ((row: any) => number),
      /**
       * 根据 row 获取相对于当前数据中的索引
       * @param {Row} row 行对象
       */
      getVTRowIndex: createGetRowCacheProp('_index') as ((row: any) => number),
      /**
       * 根据 row 获取渲染中的虚拟索引
       * @param {Row} row 行对象
       */
      getVMRowIndex: createGetRowCacheProp('$index') as ((row: any) => number),
      /**
       * 根据 column 获取相对于 columns 中的索引
       * @param {ColumnInfo} column 列配置
       */
      getColumnIndex: createGetColumnCacheProp('index'),
      /**
       * 根据 column 获取相对于当前表格列中的索引
       * @param {ColumnInfo} column 列配置
       */
      getVTColumnIndex: createGetColumnCacheProp('_index'),
      /**
       * 根据 column 获取渲染中的虚拟索引
       * @param {ColumnInfo} column 列配置
       */
      getVMColumnIndex: createGetColumnCacheProp('$index'),
      /**
       * 创建 data 对象
       * 对于某些特殊场景可能会用到，会自动对数据的字段名进行检测，如果不存在就自动定义
       * @param {Array} records 新数据
       */
      createData (records) {
        return nextTick().then(() => {
          return reactive($xeTable.defineField(records))
        })
      },
      /**
       * 创建 Row|Rows 对象
       * 对于某些特殊场景需要对数据进行手动插入时可能会用到
       * @param {Array/Object} records 新数据
       */
      createRow (records) {
        const isArr = XEUtils.isArray(records)
        if (!isArr) {
          records = [records || {}]
        }
        return $xeTable.createData(records).then((rows) => isArr ? rows : rows[0])
      },
      /**
       * 还原数据
       * 如果不传任何参数，则还原整个表格
       * 如果传 row 则还原一行
       * 如果传 rows 则还原多行
       * 如果还额外传了 field 则还原指定的单元格数据
       */
      revertData (rows: any, field) {
        const { keepSource, treeConfig } = props
        const { fullAllDataRowIdData, fullDataRowIdData, tableSourceData, sourceDataRowIdData, tableFullData, afterFullData, removeRowMaps } = internalData
        const treeOpts = computeTreeOpts.value
        const { transform } = treeOpts
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        if (!keepSource) {
          errLog('vxe.error.reqProp', ['keep-source'])
          return nextTick()
        }
        let targetRows = rows
        if (rows) {
          if (!XEUtils.isArray(rows)) {
            targetRows = [rows]
          }
        } else {
          targetRows = XEUtils.toArray($xeTable.getUpdateRecords())
        }
        let reDelFlag = false
        if (targetRows.length) {
          targetRows.forEach((item: any) => {
            const rowid = handleGetRowId(item)
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              const row = rowRest.row
              if (!$xeTable.isInsertByRow(row)) {
                const oRow = sourceDataRowIdData[rowid]
                if (oRow && row) {
                  if (field) {
                    XEUtils.set(row, field, XEUtils.clone(XEUtils.get(oRow, field), true))
                  } else {
                    XEUtils.destructuring(row, XEUtils.clone(oRow, true))
                  }
                  if (!fullDataRowIdData[rowid] && $xeTable.isRemoveByRow(row)) {
                    if (removeRowMaps[rowid]) {
                      delete removeRowMaps[rowid]
                    }
                    tableFullData.unshift(row)
                    afterFullData.unshift(row)
                    reDelFlag = true
                  }
                }
              }
            }
          })
        }
        if (rows) {
          if (reDelFlag) {
            reactData.removeRowFlag++
            $xeTable.updateFooter()
            $xeTable.cacheRowMap(false)
            $xeTable.handleTableData(treeConfig && transform)
            if (!(treeConfig && transform)) {
              $xeTable.updateAfterDataIndex()
            }
            $xeTable.checkSelectionStatus()
            if (reactData.scrollYLoad) {
              $xeTable.updateScrollYSpace()
            }
          }
          return nextTick().then(() => {
            $xeTable.updateCellAreas()
            return handleLazyRecalculate(false, true, true)
          })
        }
        return $xeTable.reloadData(tableSourceData)
      },
      /**
       * 清空单元格内容
       * 如果不创参数，则清空整个表格内容
       * 如果传 row 则清空一行内容
       * 如果传 rows 则清空多行内容
       * 如果还额外传了 field 则清空指定单元格内容
       * @param {Array/Row} rows 行数据
       * @param {String} field 字段名
       */
      clearData (rows: any, field: string) {
        const { tableFullData, visibleColumn } = internalData
        if (!arguments.length) {
          rows = tableFullData
        } else if (rows && !XEUtils.isArray(rows)) {
          rows = [rows]
        }
        if (field) {
          rows.forEach((row: any) => XEUtils.set(row, field, null))
        } else {
          rows.forEach((row: any) => {
            visibleColumn.forEach((column) => {
              if (column.field) {
                setCellValue(row, column, null)
              }
            })
          })
        }
        return nextTick()
      },
      getCellElement (row, fieldOrColumn) {
        const { elemStore } = internalData
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (!column) {
          return null
        }
        const rowid = getRowid($xeTable, row)
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        let bodyElem
        if (column) {
          if (column.fixed) {
            if (column.fixed === 'left') {
              if (leftScrollElem) {
                bodyElem = leftScrollElem
              }
            } else {
              if (rightScrollElem) {
                bodyElem = rightScrollElem
              }
            }
          }
          if (!bodyElem) {
            bodyElem = bodyScrollElem
          }
          if (bodyElem) {
            return bodyElem.querySelector(`.vxe-body--row[rowid="${rowid}"] .${column.id}`)
          }
        }
        return null
      },
      getCellLabel (row, fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (!column) {
          return null
        }
        const { editConfig } = props
        const { formatter, editRender, cellRender } = column
        // formatter > tableCellFormatter
        const renderOpts = formatter ? null : (editConfig && isEnableConf(editRender) ? editRender : (isEnableConf(cellRender) ? cellRender : null))
        const compConf = renderOpts ? renderer.get(renderOpts.name) : null
        const tcFormatter = compConf ? compConf.tableCellFormatter : null
        const cellValue = getCellValue(row, column)
        let cellLabel = cellValue
        if (formatter || tcFormatter) {
          let formatData: Record<string, VxeTableDefines.RowCacheFormatObj> | undefined
          const { fullAllDataRowIdData } = internalData
          const rowid = getRowid($xeTable, row)
          const colid = column.id
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            formatData = rowRest.formatData
            if (!formatData) {
              formatData = fullAllDataRowIdData[rowid].formatData = {}
            }
            if (rowRest && formatData[colid]) {
              if (formatData[colid].value === cellValue) {
                return formatData[colid].label
              }
            }
          }
          const formatParams = {
            $table: $xeTable,
            cellValue,
            row,
            rowIndex: $xeTable.getRowIndex(row),
            column,
            columnIndex: $xeTable.getColumnIndex(column)
          }
          if (formatter) {
            if (XEUtils.isString(formatter)) {
              const gFormatOpts = formats.get(formatter)
              const tcFormatMethod = gFormatOpts ? (gFormatOpts.tableCellFormatMethod || gFormatOpts.cellFormatMethod) : null
              cellLabel = tcFormatMethod ? tcFormatMethod(formatParams) : ''
            } else if (XEUtils.isArray(formatter)) {
              const gFormatOpts = formats.get(formatter[0])
              const tcFormatMethod = gFormatOpts ? (gFormatOpts.tableCellFormatMethod || gFormatOpts.cellFormatMethod) : null
              cellLabel = tcFormatMethod ? tcFormatMethod(formatParams, ...formatter.slice(1)) : ''
            } else {
              cellLabel = formatter(formatParams)
            }
          } else if (renderOpts && tcFormatter) {
            cellLabel = tcFormatter(renderOpts, formatParams)
          }
          if (formatData) {
            formatData[colid] = { value: cellValue, label: cellLabel }
          }
        }
        return cellLabel
      },
      updateCellLabel (row, fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (!column) {
          return null
        }
        const { fullAllDataRowIdData } = internalData
        const rowid = getRowid($xeTable, row)
        if (rowid) {
          const colid = column.id
          const rowid = getRowid($xeTable, row)
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            const formatData = rowRest.formatData
            if (formatData) {
              delete formatData[colid]
            }
          }
        }
        return $xeTable.getFooterCellLabel(row, column)
      },
      clearFormatterCache (isUpdate) {
        const { tableData, tableColumn } = reactData
        const { fullAllDataRowIdData } = internalData
        XEUtils.each(fullAllDataRowIdData, (rowRest: VxeTableDefines.FooterRowCacheItem) => {
          if (rowRest.formatData) {
            rowRest.formatData = undefined
          }
        })
        if (isUpdate) {
          tableData.forEach(row => {
            tableColumn.forEach(column => {
              $xeTable.getCellLabel(row, column)
            })
          })
        }
        return nextTick()
      },
      getFooterCellLabel (row, fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (!column) {
          return null
        }
        const { footerFormatter } = column
        const _columnIndex = $xeTable.getVTColumnIndex(column)
        let itemValue = ''
        // 兼容老模式
        if (XEUtils.isArray(row)) {
          itemValue = row[_columnIndex]
        } else {
          itemValue = XEUtils.get(row, column.field)
        }
        let cellLabel: any = itemValue
        if (footerFormatter) {
          let formatData: Record<string, VxeTableDefines.RowCacheFormatObj> | undefined
          const { footerTableData } = reactData
          const { footerFullDataRowData } = internalData
          const colid = column.id
          const $rowIndex = footerTableData.indexOf(row)
          let rowRest: any = null
          if ($rowIndex > -1) {
            rowRest = footerFullDataRowData[$rowIndex]
            if (!rowRest) {
              rowRest = footerFullDataRowData[$rowIndex] = {}
            }
            formatData = rowRest.formatData
            if (!formatData) {
              formatData = footerFullDataRowData[$rowIndex].formatData = {}
            }
            if (rowRest && formatData[colid]) {
              if (formatData[colid].value === itemValue) {
                return formatData[colid].label
              }
            }
          }
          const footerFormatParams = {
            $table: $xeTable,
            cellValue: itemValue,
            itemValue,
            row,
            items: row,
            $rowIndex,
            column,
            _columnIndex,
            columnIndex: $xeTable.getColumnIndex(column)
          }
          if (XEUtils.isString(footerFormatter)) {
            const gFormatOpts = formats.get(footerFormatter)
            const fcFormatMethod = gFormatOpts ? gFormatOpts.tableFooterCellFormatMethod : null
            cellLabel = fcFormatMethod ? fcFormatMethod(footerFormatParams) : ''
          } else if (XEUtils.isArray(footerFormatter)) {
            const gFormatOpts = formats.get(footerFormatter[0])
            const fcFormatMethod = gFormatOpts ? gFormatOpts.tableFooterCellFormatMethod : null
            cellLabel = fcFormatMethod ? fcFormatMethod(footerFormatParams, ...footerFormatter.slice(1)) : ''
          } else {
            cellLabel = footerFormatter(footerFormatParams)
          }
          if (formatData) {
            formatData[colid] = { value: itemValue, label: cellLabel }
          }
        }
        return cellLabel
      },
      updateFooterCellLabel (row, fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (!column) {
          return null
        }
        const { footerTableData } = reactData
        const { footerFullDataRowData } = internalData
        const colid = column.id
        const $rowIndex = footerTableData.indexOf(row)
        const rowRest = footerFullDataRowData[$rowIndex]
        if (rowRest) {
          const formatData = rowRest.formatData
          if (formatData) {
            delete formatData[colid]
          }
        }
        return $xeTable.getFooterCellLabel(row, column)
      },
      clearFooterFormatterCache (isUpdate) {
        const { tableData, tableColumn } = reactData
        const { footerFullDataRowData } = internalData
        XEUtils.each(footerFullDataRowData, (rowRest: VxeTableDefines.FooterRowCacheItem) => {
          if (rowRest.formatData) {
            rowRest.formatData = undefined
          }
        })
        if (isUpdate) {
          tableData.forEach(row => {
            tableColumn.forEach(column => {
              $xeTable.getFooterCellLabel(row, column)
            })
          })
        }
        return nextTick()
      },
      /**
       * 检查是否为临时行数据
       */
      isInsertByRow (row) {
        const rowid = getRowid($xeTable, row)
        return !!reactData.insertRowFlag && !!internalData.insertRowMaps[rowid]
      },
      isRemoveByRow (row) {
        const rowid = getRowid($xeTable, row)
        return !!reactData.removeRowFlag && !!internalData.removeRowMaps[rowid]
      },
      /**
       * 删除所有新增的临时数据
       */
      removeInsertRow () {
        const { insertRowMaps } = internalData
        return $xeTable.remove(XEUtils.values(insertRowMaps))
      },
      /**
       * 检查行或列数据是否发生改变
       */
      isUpdateByRow (rowidOrRow, field) {
        const { keepSource } = props
        const { fullDataRowIdData, sourceDataRowIdData } = internalData
        const keepFields = computeKeepFields.value
        if (keepSource) {
          const rowid = XEUtils.isString(rowidOrRow) || XEUtils.isNumber(rowidOrRow) ? rowidOrRow : getRowid($xeTable, rowidOrRow)
          const rowRest = fullDataRowIdData[rowid]
          // 新增的数据不需要检测
          if (!rowRest) {
            return false
          }
          const row = rowRest.row
          const oRow = sourceDataRowIdData[rowid]
          if (oRow) {
            if (arguments.length > 1) {
              return !eqCellValue(oRow, row, field as string)
            }
            for (let i = 0; i < keepFields.length; i++) {
              if (!eqCellValue(oRow, row, keepFields[i])) {
                return true
              }
            }
          }
        }
        return false
      },
      /**
       * 获取表格的可视列，也可以指定索引获取列
       * @param {Number} columnIndex 索引
       */
      getColumns (columnIndex?: number): any {
        const { visibleColumn } = internalData
        return XEUtils.isUndefined(columnIndex) ? visibleColumn.slice(0) : visibleColumn[columnIndex]
      },
      /**
       * 根据列获取列的唯一主键
       */
      getColid (fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        return column ? column.id : null
      },
      /**
       * 根据列的唯一主键获取列
       * @param {String} colid 列主键
       */
      getColumnById (colid) {
        const { fullColumnIdData } = internalData
        return colid && fullColumnIdData[colid] ? fullColumnIdData[colid].column : null
      },
      /**
       * 根据列的字段名获取列
       * @param {String} field 字段名
       */
      getColumnByField (field) {
        const fullColumnFieldData = internalData.fullColumnFieldData
        return field && fullColumnFieldData[field] ? fullColumnFieldData[field].column : null
      },
      getParentColumn (fieldOrColumn) {
        const { fullColumnIdData } = internalData
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        return column && column.parentId && fullColumnIdData[column.parentId] ? fullColumnIdData[column.parentId].column : null
      },
      /**
       * 获取当前表格的列
       * 收集到的全量列、全量表头列、处理条件之后的全量表头列、当前渲染中的表头列
       */
      getTableColumn () {
        return {
          collectColumn: internalData.collectColumn.slice(0),
          fullColumn: internalData.tableFullColumn.slice(0),
          visibleColumn: internalData.visibleColumn.slice(0),
          tableColumn: reactData.tableColumn.slice(0)
        }
      },
      /**
       * 移动列到指定列的位置
       * @param fieldOrColumn
       * @param targetFieldOrColumn
       * @param options
       */
      moveColumnTo (fieldOrColumn, targetFieldOrColumn, options) {
        const { fullColumnIdData, visibleColumn } = internalData
        const { dragToChild, dragPos, isCrossDrag } = Object.assign({}, options)
        const dragCol = handleFieldOrColumn($xeTable, fieldOrColumn)
        let prevDragCol: VxeTableDefines.ColumnInfo | null = null
        const colRest = dragCol ? fullColumnIdData[dragCol.id] : null
        let defPos: 'left' | 'right' = 'left'
        if (XEUtils.isNumber(targetFieldOrColumn)) {
          if (colRest && targetFieldOrColumn) {
            let currList = colRest.items
            let offsetIndex = colRest._index + targetFieldOrColumn
            if (isCrossDrag) {
              currList = visibleColumn
              offsetIndex = colRest._index + targetFieldOrColumn
            }
            if (offsetIndex > 0 && offsetIndex < currList.length - 1) {
              prevDragCol = currList[offsetIndex]
            }
            if (targetFieldOrColumn > 0) {
              defPos = 'right'
            }
          }
        } else {
          prevDragCol = handleFieldOrColumn($xeTable, targetFieldOrColumn)
          const targetColRest = prevDragCol ? fullColumnIdData[prevDragCol.id] : null
          if (colRest && targetColRest) {
            if (targetColRest._index > colRest._index) {
              defPos = 'right'
            }
          }
        }
        return $xeTable.handleColDragSwapEvent(null, true, dragCol, prevDragCol, dragPos || defPos, dragToChild === true)
      },
      /**
       * 移动行到指定行的位置
       * @param rowidOrRow
       * @param targetRowidOrRow
       * @param options
       */
      moveRowTo (rowidOrRow, targetRowidOrRow, options) {
        const { treeConfig } = props
        const { fullAllDataRowIdData, afterFullData } = internalData
        const { dragToChild, dragPos, isCrossDrag } = Object.assign({}, options)
        const treeOpts = computeTreeOpts.value
        const dragRow = handleRowidOrRow($xeTable, rowidOrRow)
        let prevDragRow: any = null
        let defPos: 'top' | 'bottom' = 'top'
        const rowRest = dragRow ? fullAllDataRowIdData[getRowid($xeTable, dragRow)] : null
        if (XEUtils.isNumber(targetRowidOrRow)) {
          if (rowRest && targetRowidOrRow) {
            let currList = afterFullData
            let offsetIndex = rowRest._index + targetRowidOrRow
            if (treeConfig) {
              currList = rowRest.items
              if (treeOpts.transform) {
                offsetIndex = rowRest.treeIndex + targetRowidOrRow
                if (isCrossDrag) {
                  currList = afterFullData
                  offsetIndex = rowRest._index + targetRowidOrRow
                }
              }
            }
            if (offsetIndex >= 0 && offsetIndex <= currList.length - 1) {
              prevDragRow = currList[offsetIndex]
            }
            if (targetRowidOrRow > 0) {
              defPos = 'bottom'
            }
          }
        } else {
          prevDragRow = handleRowidOrRow($xeTable, targetRowidOrRow)
          const targetRowRest = prevDragRow ? fullAllDataRowIdData[getRowid($xeTable, prevDragRow)] : null
          if (rowRest && targetRowRest) {
            if (targetRowRest._index > rowRest._index) {
              defPos = 'bottom'
            }
          }
        }
        const rest = $xeTable.handleRowDragSwapEvent(null, true, dragRow, prevDragRow, dragPos || defPos, dragToChild === true)
        return rest
      },
      /**
       * 获取表格的全量列
       */
      getFullColumns () {
        const { collectColumn } = internalData
        return collectColumn.slice(0)
      },
      /**
       * 获取数据，和 data 的行为一致，也可以指定索引获取数据
       */
      getData (rowIndex?: number) {
        const tableSynchData = props.data || internalData.tableSynchData
        return XEUtils.isUndefined(rowIndex) ? tableSynchData.slice(0) : tableSynchData[rowIndex]
      },
      /**
       * 用于多选行，获取已选中的数据
       */
      getCheckboxRecords (isFull) {
        const { treeConfig } = props
        const { updateCheckboxFlag } = reactData
        const { tableFullData, afterFullData, tableFullTreeData, fullDataRowIdData, afterFullRowMaps, selectCheckboxMaps } = internalData
        const treeOpts = computeTreeOpts.value
        const checkboxOpts = computeCheckboxOpts.value
        const { transform, mapChildrenField } = treeOpts
        const { checkField } = checkboxOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        let rowList: any[] = []
        if (updateCheckboxFlag) {
          if (checkField) {
            if (treeConfig) {
              // 如果开启 transform 默认就是完整数据
              const currTableData = isFull ? (transform ? tableFullTreeData : tableFullData) : (transform ? tableFullTreeData : afterFullData)
              rowList = XEUtils.filterTree(currTableData, row => XEUtils.get(row, checkField), { children: transform ? mapChildrenField : childrenField })
            } else {
              const currTableData = isFull ? tableFullData : afterFullData
              rowList = currTableData.filter((row) => XEUtils.get(row, checkField))
            }
          } else {
            const currMaps = isFull || (treeConfig && !transform) ? fullDataRowIdData : afterFullRowMaps
            XEUtils.each(selectCheckboxMaps, (row, rowid) => {
              if (currMaps[rowid]) {
                rowList.push(fullDataRowIdData[rowid].row)
              }
            })
          }
        }
        return rowList
      },
      /**
       * 只对 tree-config 有效，获取行的子级
       */
      getTreeRowChildren (rowOrRowid) {
        const { treeConfig } = props
        const { fullAllDataRowIdData } = internalData
        const treeOpts = computeTreeOpts.value
        const { transform, mapChildrenField } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        if (rowOrRowid && treeConfig) {
          let rowid
          if (XEUtils.isString(rowOrRowid)) {
            rowid = rowOrRowid
          } else {
            rowid = getRowid($xeTable, rowOrRowid)
          }
          if (rowid) {
            const rest = fullAllDataRowIdData[rowid]
            const row = rest ? rest.row : null
            if (row) {
              return row[transform ? mapChildrenField : childrenField] || []
            }
          }
        }
        return []
      },
      /**
       * 只对 tree-config 有效，用于树形结构，获取指定行的层级
       */
      getTreeRowLevel (rowOrRowid) {
        const { treeConfig } = props
        const { fullAllDataRowIdData } = internalData
        if (rowOrRowid && treeConfig) {
          let rowid
          if (XEUtils.isString(rowOrRowid)) {
            rowid = rowOrRowid
          } else {
            rowid = getRowid($xeTable, rowOrRowid)
          }
          if (rowid) {
            const rest = fullAllDataRowIdData[rowid]
            if (rest) {
              return rest.level
            }
          }
        }
        return -1
      },
      /**
       * 只对 tree-config 有效，获取行的父级
       */
      getTreeParentRow (rowOrRowid) {
        const { treeConfig } = props
        const { fullAllDataRowIdData } = internalData
        if (rowOrRowid && treeConfig) {
          let rowid
          if (XEUtils.isString(rowOrRowid)) {
            rowid = rowOrRowid
          } else {
            rowid = getRowid($xeTable, rowOrRowid)
          }
          if (rowid) {
            const rest = fullAllDataRowIdData[rowid]
            if (rest) {
              return rest.parent
            }
          }
        }
        return null
      },
      getParentRow (rowOrRowid) {
        warnLog('vxe.error.delFunc', ['getParentRow', 'getTreeParentRow'])
        return $xeTable.getTreeParentRow(rowOrRowid)
      },
      /**
       * 根据行的唯一主键获取行
       * @param {String/Number} rowid 行主键
       */
      getRowById (cellValue) {
        const { fullAllDataRowIdData } = internalData
        const rowid = XEUtils.eqNull(cellValue) ? '' : encodeURIComponent(cellValue || '')
        return fullAllDataRowIdData[rowid] ? fullAllDataRowIdData[rowid].row : null
      },
      /**
       * 根据行获取行的唯一主键
       * @param {Row} row 行对象
       */
      getRowid (row) {
        return getRowid($xeTable, row)
      },
      /**
       * 获取处理后的表格数据
       * 如果存在筛选条件，继续处理
       * 如果存在排序，继续处理
       */
      getTableData () {
        const { tableData, footerTableData } = reactData
        const { tableFullData, afterFullData, tableFullTreeData } = internalData
        return {
          fullData: props.treeConfig ? tableFullTreeData.slice(0) : tableFullData.slice(0),
          visibleData: afterFullData.slice(0),
          tableData: tableData.slice(0),
          footerData: footerTableData.slice(0)
        }
      },
      /**
       * 获取表格的全量数据，如果是 tree-config 则返回带层级的树结构
       */
      getFullData () {
        const { treeConfig } = props
        const { tableFullData, tableFullTreeData } = internalData
        if (treeConfig) {
          return tableFullTreeData.slice(0)
        }
        return tableFullData.slice(0)
      },
      /**
       * 设置为固定列
       */
      setColumnFixed (fieldOrColumn, fixed) {
        let status = false
        const cols = XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : [fieldOrColumn]
        const columnOpts = computeColumnOpts.value
        const isMaxFixedColumn = computeIsMaxFixedColumn.value
        for (let i = 0; i < cols.length; i++) {
          const item = cols[i]
          const column = handleFieldOrColumn($xeTable, item)
          const targetColumn = getRootColumn($xeTable, column as any)
          if (targetColumn && targetColumn.fixed !== fixed) {
            // 是否超过最大固定列数量
            if (!targetColumn.fixed && isMaxFixedColumn) {
              if (VxeUI.modal) {
                VxeUI.modal.message({
                  status: 'error',
                  content: getI18n('vxe.table.maxFixedCol', [columnOpts.maxFixedSize])
                })
              }
              return nextTick()
            }
            XEUtils.eachTree([targetColumn], (column) => {
              column.fixed = fixed
              column.renderFixed = fixed
            })
            $xeTable.saveCustomStore('update:fixed')
            if (!status) {
              status = true
            }
          }
        }
        if (status) {
          return $xeTable.refreshColumn()
        }
        return nextTick()
      },
      /**
       * 取消指定固定列
       */
      clearColumnFixed (fieldOrColumn) {
        let status = false
        const cols = XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : [fieldOrColumn]
        cols.forEach(item => {
          const column = handleFieldOrColumn($xeTable, item)
          const targetColumn = getRootColumn($xeTable, column as any)
          if (targetColumn && targetColumn.fixed) {
            XEUtils.eachTree([targetColumn], (column) => {
              column.fixed = null
              column.renderFixed = null
            })
            $xeTable.saveCustomStore('update:fixed')
            if (!status) {
              status = true
            }
          }
        })
        if (status) {
          return $xeTable.refreshColumn()
        }
        return nextTick()
      },
      /**
       * 隐藏指定列
       */
      hideColumn: handleColumnVisible(false),
      /**
       * 显示指定列
       */
      showColumn: handleColumnVisible(true),
      setColumnWidth (fieldOrColumn, width) {
        const { elemStore } = internalData
        let status = false
        const cols = XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : [fieldOrColumn]
        let cWidth = XEUtils.toInteger(width)
        if (isScale(width)) {
          const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
          const bodyWidth = bodyScrollElem ? bodyScrollElem.clientWidth - 1 : 0
          cWidth = Math.floor(cWidth * bodyWidth)
        }
        if (cWidth) {
          cols.forEach(item => {
            const column = handleFieldOrColumn($xeTable, item)
            if (column) {
              column.resizeWidth = cWidth
              if (!status) {
                status = true
              }
            }
          })
          if (status) {
            return $xeTable.refreshColumn().then(() => {
              return { status }
            })
          }
        }
        return nextTick().then(() => {
          return { status }
        })
      },
      getColumnWidth (fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (column) {
          return column.renderWidth
        }
        return 0
      },
      /**
       * 手动重置列的显示隐藏、列宽拖动的状态、固定列、排序列；
       * 如果为 true 则重置所有状态
       * 如果已关联工具栏，则会同步更新
       */
      resetColumn (options) {
        warnLog('vxe.error.delFunc', ['resetColumn', 'resetCustom'])
        return $xeTable.resetCustom(options)
      },
      /**
       * 刷新列信息
       * 将固定的列左边、右边分别靠边
       * 如果传 true 则会检查列顺序并排序
       */
      refreshColumn (initSort) {
        if (initSort) {
          handleUpdateColumn()
        }
        return parseColumns(true).then(() => {
          return $xeTable.refreshScroll()
        }).then(() => {
          return handleLazyRecalculate(false, true, true)
        })
      },
      setRowHeightConf (heightConf) {
        const { fullAllDataRowIdData } = internalData
        let status = false
        if (heightConf) {
          XEUtils.each(heightConf, (height, rowid) => {
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              const rHeight = XEUtils.toInteger(height)
              if (rHeight) {
                rowRest.resizeHeight = rHeight
                if (!status) {
                  status = true
                }
              }
            }
          })
          if (status) {
            internalData.isResizeCellHeight = true
            reactData.resizeHeightFlag++
          }
        }
        return nextTick().then(() => {
          updateRowOffsetTop()
          return { status }
        })
      },
      getRowHeightConf (isFull) {
        const { fullAllDataRowIdData, afterFullData } = internalData
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const rest: Record<string, number> = {}
        afterFullData.forEach(row => {
          const rowid = handleGetRowId(row)
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            const resizeHeight = rowRest.resizeHeight
            if (resizeHeight || isFull) {
              const currCellHeight = resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight
              rest[rowid] = currCellHeight
            }
          }
        })
        return rest
      },
      recalcRowHeight (rowOrId) {
        const { fullAllDataRowIdData } = internalData
        const rows = XEUtils.isArray(rowOrId) ? rowOrId : [rowOrId]
        const el = refElem.value
        if (el) {
          const { handleGetRowId } = createHandleGetRowId($xeTable)
          el.setAttribute('data-calc-row', 'Y')
          rows.forEach(row => {
            const rowid = XEUtils.isString(row) || XEUtils.isNumber(row) ? row : handleGetRowId(row)
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              rowRest.resizeHeight = calcCellAutoHeight(rowRest, el)
            }
            el.removeAttribute('data-calc-row')
          })
          reactData.calcCellHeightFlag++
        }
        return nextTick()
      },
      setRowHeight (rowOrId, height) {
        const { fullAllDataRowIdData } = internalData
        let status = false
        const rows = XEUtils.isArray(rowOrId) ? rowOrId : [rowOrId]
        let rHeight = XEUtils.toInteger(height)
        if (isScale(height)) {
          const tableBody = refTableBody.value
          const bodyElem = tableBody ? tableBody.$el as HTMLDivElement : null
          const bodyHeight = bodyElem ? bodyElem.clientHeight - 1 : 0
          rHeight = Math.floor(rHeight * bodyHeight)
        }
        if (rHeight) {
          const { handleGetRowId } = createHandleGetRowId($xeTable)
          rows.forEach(row => {
            const rowid = XEUtils.isString(row) || XEUtils.isNumber(row) ? row : handleGetRowId(row)
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              rowRest.resizeHeight = rHeight
              if (!status) {
                status = true
              }
            }
          })
          if (status) {
            internalData.isResizeCellHeight = true
            reactData.resizeHeightFlag++
          }
        }
        return nextTick().then(() => {
          return { status }
        })
      },
      getRowHeight (rowOrId) {
        const { fullAllDataRowIdData } = internalData
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const rowid = XEUtils.isString(rowOrId) || XEUtils.isNumber(rowOrId) ? rowOrId : getRowid($xeTable, rowOrId)
        const rowRest = fullAllDataRowIdData[rowid]
        if (rowRest) {
          return rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight
        }
        return 0
      },
      /**
       * 刷新滚动操作，手动同步滚动相关位置（对于某些特殊的操作，比如滚动条错位、固定列不同步）
       */
      refreshScroll () {
        const { elemStore, lastScrollLeft, lastScrollTop } = internalData
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value
        return new Promise<void>(resolve => {
          // 还原滚动条位置
          if (lastScrollLeft || lastScrollTop) {
            return restoreScrollLocation($xeTable, lastScrollLeft, lastScrollTop).then(() => {
              // 存在滚动行为未结束情况
              setTimeout(resolve, 10)
            })
          }
          internalData.intoRunScroll = true
          // 重置
          setScrollTop(yHandleEl, lastScrollTop)
          setScrollTop(bodyScrollElem, lastScrollTop)
          setScrollTop(leftScrollElem, lastScrollTop)
          setScrollTop(rightScrollElem, lastScrollTop)

          setScrollLeft(xHandleEl, lastScrollLeft)
          setScrollLeft(bodyScrollElem, lastScrollLeft)
          setScrollLeft(headerScrollElem, lastScrollLeft)
          setScrollLeft(footerScrollElem, lastScrollLeft)
          // 存在滚动行为未结束情况
          setTimeout(() => {
            internalData.intoRunScroll = false
            resolve()
          }, 10)
        })
      },
      /**
       * 重新渲染布局
       * 刷新布局
       */
      recalculate (reFull?: boolean) {
        const isForce = !!reFull
        return handleLazyRecalculate(isForce, isForce, isForce)
      },
      openTooltip (target, content) {
        const $commTip = refCommTooltip.value
        if ($commTip && $commTip.open) {
          return $commTip.open(target, content)
        }
        return nextTick()
      },
      /**
       * 关闭 tooltip
       */
      closeTooltip () {
        const { tooltipStore } = reactData
        const $tooltip = refTooltip.value
        const $commTip = refCommTooltip.value
        if (tooltipStore.visible) {
          Object.assign(tooltipStore, {
            row: null,
            column: null,
            content: null,
            visible: false,
            type: null,
            currOpts: {}
          })
          if ($tooltip && $tooltip.close) {
            $tooltip.close()
          }
        }
        if ($commTip && $commTip.close) {
          $commTip.close()
        }
        return nextTick()
      },
      /**
       * 判断列头复选框是否被选中
       */
      isAllCheckboxChecked () {
        return reactData.isAllSelected
      },
      /**
       * 判断列头复选框是否被半选
       */
      isAllCheckboxIndeterminate () {
        return !reactData.isAllSelected && reactData.isIndeterminate
      },
      /**
       * 获取复选框半选状态的行数据
       */
      getCheckboxIndeterminateRecords (isFull) {
        const { treeConfig } = props
        const { fullDataRowIdData, treeIndeterminateRowMaps } = internalData
        if (treeConfig) {
          const fullRest: any[] = []
          const defRest: any[] = []
          XEUtils.each(treeIndeterminateRowMaps, (item, rowid) => {
            if (item) {
              fullRest.push(item)
              if (fullDataRowIdData[rowid]) {
                defRest.push(item)
              }
            }
          })
          if (isFull) {
            return fullRest
          }
          return defRest
        }
        return []
      },
      /**
       * 用于多选行，设置行为选中状态，第二个参数为选中与否
       * @param {Array/Row} rows 行数据
       * @param {Boolean} value 是否选中
       */
      setCheckboxRow (rows, checked) {
        if (rows && !XEUtils.isArray(rows)) {
          rows = [rows]
        }
        return handleCheckedCheckboxRow(rows, checked, true)
      },
      setCheckboxRowKey (keys: any, checked) {
        const { fullAllDataRowIdData } = internalData
        if (!XEUtils.isArray(keys)) {
          keys = [keys]
        }
        const rows: any = []
        keys.forEach((rowid: string) => {
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            rows.push(rowRest.row)
          }
        })
        return handleCheckedCheckboxRow(rows, checked, true)
      },
      isCheckedByCheckboxRow (row) {
        const { updateCheckboxFlag } = reactData
        const { selectCheckboxMaps } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField } = checkboxOpts
        if (checkField) {
          return XEUtils.get(row, checkField)
        }
        return !!updateCheckboxFlag && !!selectCheckboxMaps[getRowid($xeTable, row)]
      },
      isCheckedByCheckboxRowKey (rowid: any) {
        const { updateCheckboxFlag } = reactData
        const { fullAllDataRowIdData, selectCheckboxMaps } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField } = checkboxOpts
        if (checkField) {
          const rowRest = fullAllDataRowIdData[rowid]
          if (rowRest) {
            return XEUtils.get(rowRest.row, checkField)
          }
          return false
        }
        return !!updateCheckboxFlag && !!selectCheckboxMaps[rowid]
      },
      isIndeterminateByCheckboxRow (row) {
        const { treeIndeterminateRowMaps } = internalData
        return !!treeIndeterminateRowMaps[getRowid($xeTable, row)] && !$xeTable.isCheckedByCheckboxRow(row)
      },
      isIndeterminateByCheckboxRowKey (rowid: any) {
        const { treeIndeterminateRowMaps } = internalData
        return !!treeIndeterminateRowMaps[rowid] && !$xeTable.isCheckedByCheckboxRowKey(rowid)
      },
      /**
       * 多选，切换某一行的选中状态
       */
      toggleCheckboxRow (row) {
        const { selectCheckboxMaps } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField } = checkboxOpts
        const checked = checkField ? !XEUtils.get(row, checkField) : !selectCheckboxMaps[getRowid($xeTable, row)]
        $xeTable.handleBatchSelectRows([row], checked, true)
        $xeTable.checkSelectionStatus()
        return nextTick()
      },
      /**
       * 用于多选行，设置所有行的选中状态
       * @param {Boolean} value 是否选中
       */
      setAllCheckboxRow (value) {
        return handleCheckedAllCheckboxRow(value, true)
      },
      /**
       * 获取单选框保留选中的行
       */
      getRadioReserveRecord (isFull) {
        const { treeConfig } = props
        const { fullDataRowIdData, radioReserveRow, afterFullData } = internalData
        const radioOpts = computeRadioOpts.value
        const treeOpts = computeTreeOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        if (radioOpts.reserve && radioReserveRow) {
          const rowid = getRowid($xeTable, radioReserveRow)
          if (isFull) {
            if (!fullDataRowIdData[rowid]) {
              return radioReserveRow
            }
          } else {
            const rowkey = getRowkey($xeTable)
            if (treeConfig) {
              const matchObj = XEUtils.findTree(afterFullData, row => rowid === XEUtils.get(row, rowkey), { children: childrenField })
              if (matchObj) {
                return radioReserveRow
              }
            } else {
              if (!afterFullData.some(row => rowid === XEUtils.get(row, rowkey))) {
                return radioReserveRow
              }
            }
          }
        }
        return null
      },
      clearRadioReserve () {
        internalData.radioReserveRow = null
        return nextTick()
      },
      /**
       * 获取复选框保留选中的行
       */
      getCheckboxReserveRecords (isFull) {
        const { treeConfig } = props
        const { afterFullData, fullDataRowIdData, checkboxReserveRowMap } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const treeOpts = computeTreeOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        const reserveSelection: any[] = []
        if (checkboxOpts.reserve) {
          const { handleGetRowId } = createHandleGetRowId($xeTable)
          const afterFullIdMaps: { [key: string]: number } = {}
          if (treeConfig) {
            XEUtils.eachTree(afterFullData, row => {
              afterFullIdMaps[handleGetRowId(row)] = 1
            }, { children: childrenField })
          } else {
            afterFullData.forEach(row => {
              afterFullIdMaps[handleGetRowId(row)] = 1
            })
          }
          XEUtils.each(checkboxReserveRowMap, (oldRow, oldRowid) => {
            if (oldRow) {
              if (isFull) {
                if (!fullDataRowIdData[oldRowid]) {
                  reserveSelection.push(oldRow)
                }
              } else {
                if (!afterFullIdMaps[oldRowid]) {
                  reserveSelection.push(oldRow)
                }
              }
            }
          })
        }
        return reserveSelection
      },
      clearCheckboxReserve () {
        internalData.checkboxReserveRowMap = {}
        return nextTick()
      },
      /**
       * 多选，切换所有行的选中状态
       */
      toggleAllCheckboxRow () {
        handleCheckAllEvent(null, !reactData.isAllSelected)
        return nextTick()
      },
      /**
       * 用于多选行，手动清空用户的选择
       * 清空行为不管是否被禁用还是保留记录，都将彻底清空选中状态
       */
      clearCheckboxRow () {
        const { treeConfig } = props
        const { tableFullData } = internalData
        const treeOpts = computeTreeOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField, reserve } = checkboxOpts
        // indeterminateField 仅支持读取
        const indeterminateField = checkboxOpts.indeterminateField || checkboxOpts.halfField
        if (checkField) {
          const handleClearChecked = (item: any) => {
            if (treeConfig && indeterminateField) {
              XEUtils.set(item, indeterminateField, false)
            }
            XEUtils.set(item, checkField, false)
          }
          if (treeConfig) {
            XEUtils.eachTree(tableFullData, handleClearChecked, { children: childrenField })
          } else {
            tableFullData.forEach(handleClearChecked)
          }
        }
        if (reserve) {
          tableFullData.forEach((row) => handleCheckboxReserveRow(row, false))
        }
        reactData.isAllSelected = false
        reactData.isIndeterminate = false
        internalData.selectCheckboxMaps = {}
        internalData.treeIndeterminateRowMaps = {}
        reactData.updateCheckboxFlag++
        return nextTick()
      },
      /**
       * 用于当前行，设置某一行为高亮状态
       * @param {Row} row 行对象
       */
      setCurrentRow (row) {
        const $xeGanttView = internalData.xeGanttView
        const rowOpts = computeRowOpts.value
        const el = refElem.value
        $xeTable.clearCurrentRow()
        // $xeTable.clearCurrentColumn()
        internalData.currentRow = row
        if (rowOpts.isCurrent || props.highlightCurrentRow) {
          if (el) {
            XEUtils.arrayEach(el.querySelectorAll(`[rowid="${getRowid($xeTable, row)}"]`), elem => addClass(elem, 'row--current'))
          }
        }
        if ($xeGanttView && $xeGanttView.handleUpdateCurrentRow) {
          $xeGanttView.handleUpdateCurrentRow(row)
        }
        return nextTick()
      },
      isCheckedByRadioRow (row) {
        const { selectRadioRow } = reactData
        if (row && selectRadioRow) {
          return $xeTable.eqRow(selectRadioRow, row)
        }
        return false
      },
      isCheckedByRadioRowKey (key) {
        const { selectRadioRow } = reactData
        if (selectRadioRow) {
          return key === getRowid($xeTable, selectRadioRow)
        }
        return false
      },
      /**
       * 用于单选行，设置某一行为选中状态
       * @param {Row} row 行对象
       */
      setRadioRow (row) {
        return handleCheckedRadioRow(row, true)
      },
      /**
       * 用于单选行，设置某一行为选中状态
       * @param key 行主键
       */
      setRadioRowKey (rowid: any) {
        const { fullAllDataRowIdData } = internalData
        const rowRest = fullAllDataRowIdData[rowid]
        if (rowRest) {
          return handleCheckedRadioRow(rowRest.row, true)
        }
        return nextTick()
      },
      /**
       * 用于当前行，手动清空当前高亮的状态
       */
      clearCurrentRow () {
        const $xeGanttView = internalData.xeGanttView
        const el = refElem.value
        internalData.currentRow = null
        internalData.hoverRow = null
        if (el) {
          XEUtils.arrayEach(el.querySelectorAll('.row--current'), elem => removeClass(elem, 'row--current'))
        }
        if ($xeGanttView && $xeGanttView.handleUpdateCurrentRow) {
          $xeGanttView.handleUpdateCurrentRow()
        }
        return nextTick()
      },
      /**
       * 用于单选行，手动清空用户的选择
       */
      clearRadioRow () {
        reactData.selectRadioRow = null
        return nextTick()
      },
      /**
       * 用于当前行，获取当前行的数据
       */
      getCurrentRecord (isFull) {
        const { fullDataRowIdData, afterFullRowMaps, currentRow } = internalData
        const rowOpts = computeRowOpts.value
        if (rowOpts.isCurrent || props.highlightCurrentRow) {
          const rowid = getRowid($xeTable, currentRow)
          if (isFull) {
            if (fullDataRowIdData[rowid]) {
              return currentRow
            }
          } else {
            if (afterFullRowMaps[rowid]) {
              return currentRow
            }
          }
        }
        return null
      },
      /**
       * 用于单选行，获取当已选中的数据
       */
      getRadioRecord (isFull) {
        const { fullDataRowIdData, afterFullRowMaps } = internalData
        const { selectRadioRow } = reactData
        if (selectRadioRow) {
          const rowid = getRowid($xeTable, selectRadioRow)
          if (isFull) {
            if (fullDataRowIdData[rowid]) {
              return selectRadioRow
            }
          } else {
            if (afterFullRowMaps[rowid]) {
              return selectRadioRow
            }
          }
        }
        return null
      },
      getCurrentColumn () {
        const columnOpts = computeColumnOpts.value
        return columnOpts.isCurrent || props.highlightCurrentColumn ? reactData.currentColumn : null
      },
      /**
       * 用于当前列，设置某列行为高亮状态
       */
      setCurrentColumn (fieldOrColumn) {
        const { mouseConfig } = props
        const mouseOpts = computeMouseOpts.value
        const isMouseSelected = mouseConfig && mouseOpts.selected
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (column) {
          $xeTable.clearCurrentColumn()
          reactData.currentColumn = column
        }
        return nextTick().then(() => {
          // 更新状选中态
          if (isMouseSelected) {
            $xeTable.addCellSelectedClass()
          }
        })
      },
      /**
       * 用于当前列，手动清空当前高亮的状态
       */
      clearCurrentColumn () {
        reactData.currentColumn = null
        return nextTick()
      },
      setPendingRow (rows: any | any[], status: boolean) {
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        const { pendingRowMaps } = internalData
        if (rows && !XEUtils.isArray(rows)) {
          rows = [rows]
        }
        if (status) {
          rows.forEach((row: any) => {
            const rowid = handleGetRowId(row)
            if (rowid && !pendingRowMaps[rowid]) {
              pendingRowMaps[rowid] = row
            }
          })
        } else {
          rows.forEach((row: any) => {
            const rowid = handleGetRowId(row)
            if (rowid && pendingRowMaps[rowid]) {
              delete pendingRowMaps[rowid]
            }
          })
        }
        reactData.pendingRowFlag++
        return nextTick()
      },
      togglePendingRow (rows: any | any[]) {
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        const { pendingRowMaps } = internalData
        if (rows && !XEUtils.isArray(rows)) {
          rows = [rows]
        }
        rows.forEach((row: any) => {
          const rowid = handleGetRowId(row)
          if (rowid) {
            if (pendingRowMaps[rowid]) {
              delete pendingRowMaps[rowid]
            } else {
              pendingRowMaps[rowid] = row
            }
          }
        })
        reactData.pendingRowFlag++
        return nextTick()
      },
      hasPendingByRow (row) {
        return $xeTable.isPendingByRow(row)
      },
      isPendingByRow (row) {
        const { pendingRowMaps } = internalData
        const rowid = getRowid($xeTable, row)
        return !!pendingRowMaps[rowid]
      },
      getPendingRecords () {
        const { fullAllDataRowIdData, pendingRowMaps } = internalData
        const insertRecords: any[] = []
        XEUtils.each(pendingRowMaps, (row, rowid) => {
          if (fullAllDataRowIdData[rowid]) {
            insertRecords.push(row)
          }
        })
        return insertRecords
      },
      clearPendingRow () {
        internalData.pendingRowMaps = {}
        reactData.pendingRowFlag++
        return nextTick()
      },
      sort (sortConfs: any, sortOrder?: VxeTablePropTypes.SortOrder) {
        const sortOpts = computeSortOpts.value
        const { multiple, remote, orders } = sortOpts
        if (sortConfs) {
          if (XEUtils.isString(sortConfs)) {
            sortConfs = [
              { field: sortConfs, order: sortOrder }
            ]
          }
        }
        if (!XEUtils.isArray(sortConfs)) {
          sortConfs = [sortConfs]
        }
        if (sortConfs.length) {
          if (!multiple) {
            clearAllSort()
          }
          (multiple ? sortConfs : [sortConfs[0]]).forEach((confs: any, index: number) => {
            let { field, order } = confs
            let column = field
            if (XEUtils.isString(field)) {
              column = $xeTable.getColumnByField(field)
            }
            if (column && column.sortable) {
              if (orders && orders.indexOf(order) === -1) {
                order = getNextSortOrder(column)
              }
              if (column.order !== order) {
                column.order = order
              }
              column.sortTime = Date.now() + index
            }
          })
          // 如果是服务端排序，则跳过本地排序处理
          if (!remote) {
            $xeTable.handleTableData(true)
          }
          return nextTick().then(() => {
            updateRowOffsetTop()
            $xeTable.updateCellAreas()
            return updateStyle()
          })
        }
        return nextTick()
      },
      setSort (sortConfs, isUpdate) {
        return handleSortEvent(null, sortConfs, isUpdate)
      },
      setSortByEvent (evnt, sortConfs) {
        return handleSortEvent(evnt, sortConfs, true)
      },
      /**
       * 清空指定列的排序条件
       * 如果为空则清空所有列的排序条件
       * @param {String} fieldOrColumn 列或字段名
       */
      clearSort (fieldOrColumn) {
        const sortOpts = computeSortOpts.value
        if (fieldOrColumn) {
          const column = handleFieldOrColumn($xeTable, fieldOrColumn)
          if (column) {
            column.order = null
          }
        } else {
          clearAllSort()
        }
        if (!sortOpts.remote) {
          $xeTable.handleTableData(true)
        }
        return nextTick().then(() => {
          updateRowOffsetTop()
          return updateStyle()
        })
      },
      clearSortByEvent (evnt, fieldOrColumn) {
        const { tableFullColumn } = internalData
        const sortOpts = computeSortOpts.value
        const { multiple } = sortOpts
        const sortCols: VxeTableDefines.ColumnInfo[] = []
        let column: VxeTableDefines.ColumnInfo<any> | null = null
        if (evnt) {
          if (fieldOrColumn) {
            column = handleFieldOrColumn($xeTable, fieldOrColumn)
            if (column) {
              column.order = null
            }
          } else {
            tableFullColumn.forEach((column) => {
              if (column.order) {
                column.order = null
                sortCols.push(column)
              }
            })
          }
          if (!sortOpts.remote) {
            $xeTable.handleTableData(true)
          }

          if (!multiple) {
            column = sortCols[0]
          }

          if (column) {
            $xeTable.handleColumnSortEvent(evnt, column)
          }

          if (multiple && sortCols.length) {
            const params = { $table: $xeTable, $event: evnt, cols: sortCols, sortList: [] }
            dispatchEvent('clear-all-sort', params, evnt)
          }
        }
        return nextTick().then(() => {
          updateRowOffsetTop()
          return updateStyle()
        })
      },
      isSort (fieldOrColumn) {
        if (fieldOrColumn) {
          const column = handleFieldOrColumn($xeTable, fieldOrColumn)
          return column ? column.sortable && !!column.order : false
        }
        return $xeTable.getSortColumns().length > 0
      },
      getSortColumns () {
        const sortOpts = computeSortOpts.value
        const { multiple, chronological } = sortOpts
        const sortList: VxeTableDefines.SortCheckedParams[] = []
        const { tableFullColumn } = internalData
        tableFullColumn.forEach((column) => {
          const { field, order } = column
          if (column.sortable && order) {
            sortList.push({ column, field, property: field, order: order, sortTime: column.sortTime })
          }
        })
        if (multiple && chronological && sortList.length > 1) {
          return XEUtils.orderBy(sortList, 'sortTime')
        }
        return sortList
      },
      setFilterByEvent (evnt, fieldOrColumn, options) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (column && column.filters) {
          column.filters = toFilters(options || [], column.id)
          return $xeTable.handleColumnConfirmFilter(column, evnt)
        }
        return nextTick()
      },
      /**
       * 关闭筛选
       * @param {Event} evnt 事件
       */
      closeFilter () {
        const { filterStore } = reactData
        const { column, visible } = filterStore
        filterStore.isAllSelected = false
        filterStore.isIndeterminate = false
        filterStore.visible = false
        if (visible) {
          const field = column ? column.field : null
          dispatchEvent('filter-visible', {
            column,
            property: field,
            field,
            filterList: () => $xeTable.getCheckedFilters(),
            visible: false
          }, null)
        }
        return nextTick()
      },
      /**
       * 判断指定列是否为筛选状态，如果为空则判断所有列
       * @param {String} fieldOrColumn 字段名
       */
      isActiveFilterByColumn (fieldOrColumn) {
        const column = handleFieldOrColumn($xeTable, fieldOrColumn)
        if (column) {
          return column.filters && column.filters.some((option) => option.checked)
        }
        return $xeTable.getCheckedFilters().length > 0
      },
      isFilter (fieldOrColumn) {
        return $xeTable.isActiveFilterByColumn(fieldOrColumn)
      },
      clearFilterByEvent (evnt, fieldOrColumn) {
        const { filterStore } = reactData
        const { tableFullColumn } = internalData
        const filterOpts = computeFilterOpts.value
        const { multiple } = filterOpts
        const filterCols: VxeTableDefines.ColumnInfo[] = []
        let column: VxeTableDefines.ColumnInfo<any> | null = null
        if (fieldOrColumn) {
          column = handleFieldOrColumn($xeTable, fieldOrColumn)
          if (column) {
            $xeTable.handleClearFilter(column)
          }
        } else {
          tableFullColumn.forEach(column => {
            if (column.filters) {
              filterCols.push(column)
              $xeTable.handleClearFilter(column)
            }
          })
        }
        if (!fieldOrColumn || column !== filterStore.column) {
          Object.assign(filterStore, {
            isAllSelected: false,
            isIndeterminate: false,
            style: null,
            options: [],
            column: null,
            multiple: false, // 选项是覅多选
            visible: false
          })
        }

        if (!filterOpts.remote) {
          $xeTable.updateData()
        }

        if (!multiple) {
          column = filterCols[0]
        }

        if (column) {
          const filterList = () => $xeTable.getCheckedFilters()
          const values: any[] = []
          const datas: any[] = []
          column.filters.forEach((item: any) => {
            if (item.checked) {
              values.push(item.value)
              datas.push(item.data)
            }
          })
          const params = {
            $table: $xeTable,
            $event: evnt as Event,
            column,
            field: column.field,
            property: column.field,
            values,
            datas,
            filters: filterList,
            filterList
          }
          $xeTable.dispatchEvent('filter-change', params, evnt)
          $xeTable.dispatchEvent('clear-filter', params, evnt)
        }

        if (multiple && filterCols.length) {
          const params = { $table: $xeTable, $event: evnt, cols: filterCols, filterList: [] }
          dispatchEvent('clear-all-filter', params, evnt)
        }

        return nextTick()
      },
      /**
       * 判断展开行是否懒加载完成
       * @param {Row} row 行对象
       */
      isRowExpandLoaded (row) {
        const { fullAllDataRowIdData } = internalData
        const rowRest = fullAllDataRowIdData[getRowid($xeTable, row)]
        return rowRest && !!rowRest.expandLoaded
      },
      clearRowExpandLoaded (row) {
        const { fullAllDataRowIdData, rowExpandLazyLoadedMaps } = internalData
        const expandOpts = computeExpandOpts.value
        const { lazy } = expandOpts
        const rowid = getRowid($xeTable, row)
        const rowRest = fullAllDataRowIdData[rowid]
        if (lazy && rowRest) {
          rowRest.expandLoaded = false
          delete rowExpandLazyLoadedMaps[rowid]
        }
        reactData.rowExpandedFlag++
        return nextTick()
      },
      /**
       * 重新懒加载展开行，并展开内容
       * @param {Row} row 行对象
       */
      reloadRowExpand (row) {
        const { rowExpandLazyLoadedMaps } = internalData
        const expandOpts = computeExpandOpts.value
        const { lazy } = expandOpts
        const rowid = getRowid($xeTable, row)
        if (lazy && !rowExpandLazyLoadedMaps[rowid]) {
          $xeTable.clearRowExpandLoaded(row)
            .then(() => handleAsyncRowExpand(row))
        }
        return nextTick()
      },
      reloadExpandContent (row) {
        warnLog('vxe.error.delFunc', ['reloadExpandContent', 'reloadRowExpand'])
        // 即将废弃
        return $xeTable.reloadRowExpand(row)
      },
      /**
       * 切换展开行
       */
      toggleRowExpand (row) {
        return $xeTable.setRowExpand(row, !$xeTable.isRowExpandByRow(row))
      },
      /**
       * 设置所有行的展开与否
       * @param {Boolean} expanded 是否展开
       */
      setAllRowExpand (expanded) {
        const treeOpts = computeTreeOpts.value
        const { tableFullData, tableFullTreeData } = internalData
        const childrenField = treeOpts.children || treeOpts.childrenField
        let expandedRows: any[] = []
        if (props.treeConfig) {
          XEUtils.eachTree(tableFullTreeData, (row) => {
            expandedRows.push(row)
          }, { children: childrenField })
        } else {
          expandedRows = tableFullData
        }
        return $xeTable.setRowExpand(expandedRows, expanded)
      },
      /**
       * 设置展开行，二个参数设置这一行展开与否
       * 支持单行
       * 支持多行
       * @param {Array/Row} rows 行数据
       * @param {Boolean} expanded 是否展开
       */
      setRowExpand (rows, expanded) {
        const { expandColumn } = reactData
        let { fullAllDataRowIdData, rowExpandedMaps, rowExpandLazyLoadedMaps } = internalData
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        const expandOpts = computeExpandOpts.value
        const { reserve, lazy, accordion, toggleMethod } = expandOpts
        const lazyRests: any[] = []
        const columnIndex = expandColumn ? $xeTable.getColumnIndex(expandColumn) : -1
        const $columnIndex = expandColumn ? $xeTable.getVMColumnIndex(expandColumn) : -1
        if (rows) {
          if (!XEUtils.isArray(rows)) {
            rows = [rows]
          }
          if (accordion) {
            // 只能同时展开一个
            rowExpandedMaps = {}
            internalData.rowExpandedMaps = rowExpandedMaps
            rows = rows.slice(rows.length - 1, rows.length)
          }
          const validRows: any[] = toggleMethod ? rows.filter((row: any) => toggleMethod({ $table: $xeTable, expanded, column: expandColumn as VxeTableDefines.ColumnInfo, columnIndex, $columnIndex, row, rowIndex: $xeTable.getRowIndex(row), $rowIndex: $xeTable.getVMRowIndex(row) })) : rows
          if (expanded) {
            validRows.forEach((row) => {
              const rowid = handleGetRowId(row)
              if (!rowExpandedMaps[rowid]) {
                const rowRest = fullAllDataRowIdData[rowid]
                const isLoad = lazy && !rowRest.expandLoaded && !rowExpandLazyLoadedMaps[rowid]
                if (isLoad) {
                  lazyRests.push(handleAsyncRowExpand(row))
                } else {
                  rowExpandedMaps[rowid] = row
                }
              }
            })
          } else {
            validRows.forEach(item => {
              const rowid = handleGetRowId(item)
              if (rowExpandedMaps[rowid]) {
                delete rowExpandedMaps[rowid]
              }
            })
          }
          if (reserve) {
            validRows.forEach((row) => handleRowExpandReserve(row, expanded))
          }
        }
        reactData.rowExpandedFlag++
        return Promise.all(lazyRests)
          .then(() => nextTick())
          .then(() => handleLazyRecalculate(true, true, true))
          .then(() => {
            updateRowOffsetTop()
            updateRowExpandStyle()
            handleRowExpandScroll()
            return $xeTable.updateCellAreas()
          })
      },
      /**
       * 判断行是否为展开状态
       * @param {Row} row 行对象
       */
      isRowExpandByRow (row) {
        const { rowExpandedFlag } = reactData
        const { rowExpandedMaps } = internalData
        const rowid = getRowid($xeTable, row)
        return !!rowExpandedFlag && !!rowExpandedMaps[rowid]
      },
      isExpandByRow (row) {
        // 已废弃
        warnLog('vxe.error.delFunc', ['isExpandByRow', 'isRowExpandByRow'])
        return $xeTable.isRowExpandByRow(row)
      },
      /**
       * 手动清空展开行状态，数据会恢复成未展开的状态
       */
      clearRowExpand () {
        const { tableFullData, scrollYStore } = internalData
        const expandOpts = computeExpandOpts.value
        const { reserve } = expandOpts
        const expList = $xeTable.getRowExpandRecords()
        internalData.rowExpandedMaps = {}
        if (reserve) {
          tableFullData.forEach((row) => handleRowExpandReserve(row, false))
        }
        reactData.rowExpandedFlag++
        scrollYStore.startIndex = 0
        scrollYStore.endIndex = 1
        return nextTick().then(() => {
          if (expList.length) {
            return handleLazyRecalculate(true, true, true)
          }
        }).then(() => {
          updateRowOffsetTop()
          updateRowExpandStyle()
          handleRowExpandScroll()
          return $xeTable.updateCellAreas()
        })
      },
      clearRowExpandReserve () {
        internalData.rowExpandedReserveRowMap = {}
        return nextTick()
      },
      getRowExpandRecords () {
        const rest: any[] = []
        XEUtils.each(internalData.rowExpandedMaps, item => {
          if (item) {
            rest.push(item)
          }
        })
        return rest
      },
      setRowGroups (fieldOrColumns) {
        const { aggregateConfig, rowGroupConfig } = props
        const aggregateOpts = computeAggregateOpts.value
        const { maxGroupSize } = aggregateOpts
        if (!(aggregateConfig || rowGroupConfig)) {
          errLog('vxe.error.reqProp', ['aggregate-config'])
          return nextTick()
        }
        const confList = fieldOrColumns ? (XEUtils.isArray(fieldOrColumns) ? fieldOrColumns : [fieldOrColumns]) : []
        if (maxGroupSize && confList.length > maxGroupSize) {
          if (VxeUI.modal) {
            VxeUI.modal.message({
              status: 'error',
              content: getI18n('vxe.table.maxGroupCol', [maxGroupSize])
            })
          }
          return nextTick()
        }
        if (confList.length) {
          handleUpdateRowGroup(confList.map(fieldOrColumn => {
            return XEUtils.isString(fieldOrColumn) ? fieldOrColumn : fieldOrColumn.field
          }))
          return loadTableData(internalData.tableSynchData, false, true)
        }
        return nextTick()
      },
      getRowGroups () {
        const { aggregateConfig, rowGroupConfig } = props
        const { fullColumnFieldData } = internalData
        if (aggregateConfig || rowGroupConfig) {
          const { rowGroupList } = reactData
          return rowGroupList.map(({ field }) => {
            const colRet = fullColumnFieldData[field]
            if (colRet) {
              return colRet.column
            }
            return { field }
          })
        }
        return []
      },
      getRowGroupFields () {
        return $xeTable.getRowGroups().map(item => item.field)
      },
      clearRowGroups () {
        const { aggregateConfig, rowGroupConfig } = props
        if (!(aggregateConfig || rowGroupConfig)) {
          errLog('vxe.error.reqProp', ['aggregate-config'])
          return nextTick()
        }
        handleUpdateRowGroup([])
        return loadTableData(internalData.tableSynchData, false, true)
      },
      isRowGroupRecord (row) {
        warnLog('vxe.error.delFunc', ['isRowGroupRecord', 'isAggregateRecord'])
        return $xeTable.isAggregateRecord(row)
      },
      isRowGroupExpandByRow (row) {
        warnLog('vxe.error.delFunc', ['isRowGroupExpandByRow', 'isAggregateExpandByRow'])
        return $xeTable.isAggregateExpandByRow(row)
      },
      isAggregateRecord (row) {
        const { isRowGroupStatus } = reactData
        return isRowGroupStatus && row.isAggregate
      },
      getAggregateContentByRow (row) {
        const { isRowGroupStatus } = reactData
        return isRowGroupStatus && row && row.isAggregate ? row.groupContent : ''
      },
      getAggregateRowChildren (row) {
        const aggregateOpts = computeAggregateOpts.value
        const { childrenField, mapChildrenField } = aggregateOpts

        const { isRowGroupStatus } = reactData
        return isRowGroupStatus && row && row.isAggregate && childrenField && mapChildrenField ? (row[mapChildrenField] || []) : []
      },
      refreshAggregateCalcValues () {
        updateGroupData()
        return nextTick()
      },
      isAggregateExpandByRow (row) {
        const { rowGroupExpandedFlag } = reactData
        const { rowGroupExpandedMaps } = internalData
        return !!rowGroupExpandedFlag && !!rowGroupExpandedMaps[getRowid($xeTable, row)]
      },
      setRowGroupExpand (rows, expanded) {
        if (rows) {
          if (!XEUtils.isArray(rows)) {
            rows = [rows]
          }
          return handleRowGroupVirtualExpand(rows, expanded)
        }
        return nextTick()
      },
      setRowGroupExpandByField (groupFields, expanded) {
        const { isRowGroupStatus } = reactData
        const aggregateOpts = computeAggregateOpts.value
        const { childrenField } = aggregateOpts
        if (groupFields) {
          if (!XEUtils.isArray(groupFields)) {
            groupFields = [groupFields]
          }
          if (isRowGroupStatus) {
            const rows: any[] = []
            const gfKeys: Record<string, boolean> = {}
            groupFields.forEach(groupField => {
              gfKeys[groupField] = true
            })
            XEUtils.eachTree(internalData.afterGroupFullData, (row) => {
              if (row.isAggregate && gfKeys[row.groupField]) {
                rows.push(row)
              }
            }, { children: childrenField })
            if (rows.length) {
              return handleRowGroupVirtualExpand(rows, expanded)
            }
          }
        }
        return nextTick()
      },
      setAllRowGroupExpand (expanded) {
        const { tableFullGroupData } = internalData
        const aggregateOpts = computeAggregateOpts.value
        const { mapChildrenField } = aggregateOpts
        const rgExpandedMaps: Record<string, any> = {}
        if (expanded && mapChildrenField) {
          XEUtils.eachTree(tableFullGroupData, (row) => {
            if (row[mapChildrenField] && row[mapChildrenField].length) {
              rgExpandedMaps[getRowid($xeTable, row)] = row
            }
          }, { children: mapChildrenField })
        }
        internalData.rowGroupExpandedMaps = rgExpandedMaps
        handleVirtualTreeToList()
        $xeTable.handleTableData()
        updateAfterDataIndex()
        reactData.rowGroupExpandedFlag++
        return handleLazyRecalculate(true, true, true)
      },
      clearRowGroupExpand () {
        internalData.rowGroupExpandedMaps = {}
        handleVirtualTreeToList()
        $xeTable.handleTableData()
        updateAfterDataIndex()
        reactData.rowGroupExpandedFlag++
        return handleLazyRecalculate(true, true, true)
      },
      getTreeExpandRecords () {
        const rest: any[] = []
        XEUtils.each(internalData.treeExpandedMaps, item => {
          if (item) {
            rest.push(item)
          }
        })
        return rest
      },
      /**
       * 判断树节点是否懒加载完成
       * @param {Row} row 行对象
       */
      isTreeExpandLoaded (row) {
        const { fullAllDataRowIdData } = internalData
        const rowRest = fullAllDataRowIdData[getRowid($xeTable, row)]
        return rowRest && !!rowRest.treeLoaded
      },
      clearTreeExpandLoaded (rows: any) {
        const { fullAllDataRowIdData, treeExpandedMaps } = internalData
        const treeOpts = computeTreeOpts.value
        const { transform } = treeOpts
        if (rows) {
          if (!XEUtils.isArray(rows)) {
            rows = [rows]
          }
          rows.forEach((row: any) => {
            const rowid = getRowid($xeTable, row)
            const rowRest = fullAllDataRowIdData[rowid]
            if (rowRest) {
              rowRest.treeLoaded = false
              if (treeExpandedMaps[rowid]) {
                delete treeExpandedMaps[rowid]
              }
            }
          })
        } else {
          XEUtils.each(fullAllDataRowIdData, (rowRest) => {
            rowRest.treeLoaded = false
          })
          internalData.treeExpandedMaps = {}
        }
        if (transform) {
          handleVirtualTreeToList()
          $xeTable.handleTableData()
        }
        reactData.treeExpandedFlag++
        return nextTick()
      },
      /**
       * 重新懒加载树节点，并展开该节点
       * @param {Row} row 行对象
       */
      reloadTreeExpand (row) {
        const { treeExpandLazyLoadedMaps } = internalData
        const treeOpts = computeTreeOpts.value
        const hasChildField = treeOpts.hasChild || treeOpts.hasChildField
        const { transform, lazy } = treeOpts
        const rowid = getRowid($xeTable, row)
        if (lazy && row[hasChildField] && !treeExpandLazyLoadedMaps[rowid]) {
          return $xeTable.clearTreeExpandLoaded(row).then(() => {
            return handleAsyncTreeExpandChilds(row)
          }).then(() => {
            if (transform) {
              handleVirtualTreeToList()
              $xeTable.handleTableData()
            }
            reactData.treeExpandedFlag++
          }).then(() => {
            return $xeTable.recalculate()
          })
        }
        return nextTick()
      },
      reloadTreeChilds (row) {
        warnLog('vxe.error.delFunc', ['reloadTreeChilds', 'reloadTreeExpand'])
        // 即将废弃
        return $xeTable.reloadTreeExpand(row)
      },
      /**
       * 切换/展开树节点
       */
      toggleTreeExpand (row) {
        return $xeTable.setTreeExpand(row, !$xeTable.isTreeExpandByRow(row))
      },
      /**
       * 设置所有树节点的展开与否
       * @param {Boolean} expanded 是否展开
       */
      setAllTreeExpand (expanded: boolean) {
        const { tableFullData } = internalData
        const treeOpts = computeTreeOpts.value
        const { transform, lazy } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        const expandeds: any[] = []
        XEUtils.eachTree(tableFullData, (row) => {
          const rowChildren = row[childrenField]
          if (lazy || (rowChildren && rowChildren.length)) {
            expandeds.push(row)
          }
        }, { children: childrenField })
        return $xeTable.setTreeExpand(expandeds, expanded).then(() => {
          if (transform) {
            handleVirtualTreeToList()
            reactData.treeExpandedFlag++
            return $xeTable.recalculate()
          }
        })
      },
      /**
       * 设置展开树形节点，二个参数设置这一行展开与否
       * 支持单行
       * 支持多行
       * @param {Array/Row} rows 行数据
       * @param {Boolean} expanded 是否展开
       */
      setTreeExpand (rows, expanded) {
        const treeOpts = computeTreeOpts.value
        const { transform } = treeOpts
        if (rows) {
          if (!XEUtils.isArray(rows)) {
            rows = [rows]
          }
          if (rows.length) {
            // 如果为虚拟树
            if (transform) {
              return handleVirtualTreeExpand(rows, expanded)
            } else {
              return handleBaseTreeExpand(rows, expanded)
            }
          }
        }
        return nextTick()
      },
      /**
       * 判断行是否为树形节点展开状态
       * @param {Row} row 行对象
       */
      isTreeExpandByRow (row) {
        const { treeExpandedFlag } = reactData
        const { treeExpandedMaps } = internalData
        return !!treeExpandedFlag && !!treeExpandedMaps[getRowid($xeTable, row)]
      },
      /**
       * 手动清空树形节点的展开状态，数据会恢复成未展开的状态
       */
      clearTreeExpand () {
        const { tableFullTreeData } = internalData
        const treeOpts = computeTreeOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        const { transform, reserve } = treeOpts
        const expList = $xeTable.getTreeExpandRecords()
        internalData.treeExpandedMaps = {}
        if (reserve) {
          XEUtils.eachTree(tableFullTreeData, row => handleTreeExpandReserve(row, false), { children: childrenField })
        }
        return $xeTable.handleTableData().then(() => {
          if (transform) {
            handleVirtualTreeToList()
            $xeTable.handleTableData()
          }
          reactData.treeExpandedFlag++
        }).then(() => {
          updateTreeLineStyle()
          if (expList.length) {
            return $xeTable.recalculate()
          }
        })
      },
      clearTreeExpandReserve () {
        internalData.treeExpandedReserveRowMap = {}
        return nextTick()
      },
      /**
       * 获取表格的滚动状态
       */
      getScroll () {
        return $xeTable.getScrollData()
      },
      /**
       * 获取表格的滚动数据
       */
      getScrollData () {
        const { scrollXLoad, scrollYLoad, scrollbarHeight, scrollbarWidth } = reactData
        const { elemStore } = internalData
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const scrollTop = bodyScrollElem ? bodyScrollElem.scrollTop : 0
        const scrollLeft = bodyScrollElem ? bodyScrollElem.scrollLeft : 0
        const clientHeight = bodyScrollElem ? bodyScrollElem.clientHeight : 0
        const clientWidth = bodyScrollElem ? bodyScrollElem.clientWidth : 0
        const scrollHeight = bodyScrollElem ? bodyScrollElem.scrollHeight : 0
        const scrollWidth = bodyScrollElem ? bodyScrollElem.scrollWidth : 0
        const isTop = scrollTop <= 0
        const isBottom = scrollTop + clientHeight >= scrollHeight
        const isLeft = scrollLeft <= 0
        const isRight = scrollLeft + clientWidth >= scrollWidth
        return {
          virtualX: scrollXLoad,
          virtualY: scrollYLoad,
          isTop,
          isBottom,
          isLeft,
          isRight,
          scrollbarHeight,
          scrollbarWidth,
          scrollTop,
          scrollLeft,
          scrollHeight,
          scrollWidth,
          clientHeight,
          clientWidth
        }
      },
      /**
       * 如果有滚动条，则滚动到对应的位置
       */
      scrollTo (scrollLeft, scrollTop) {
        const { elemStore } = internalData
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value

        internalData.intoRunScroll = true

        if (scrollLeft) {
          if (!XEUtils.isNumber(scrollLeft)) {
            scrollTop = scrollLeft.top
            scrollLeft = scrollLeft.left
          }
        }
        if (XEUtils.isNumber(scrollLeft)) {
          setScrollLeft(xHandleEl, scrollLeft)
          setScrollLeft(bodyScrollElem, scrollLeft)
          setScrollLeft(headerScrollElem, scrollLeft)
          setScrollLeft(footerScrollElem, scrollLeft)
          if (reactData.scrollXLoad) {
            loadScrollXData()
          }
        }
        if (XEUtils.isNumber(scrollTop)) {
          setScrollTop(yHandleEl, scrollTop)
          setScrollTop(bodyScrollElem, scrollTop)
          setScrollTop(leftScrollElem, scrollTop)
          setScrollTop(rightScrollElem, scrollTop)
          if (reactData.scrollYLoad) {
            loadScrollYData()
          }
        }
        return new Promise<void>(resolve => {
          setTimeout(() => {
            nextTick(() => {
              internalData.intoRunScroll = false
              resolve()
            })
          }, (reactData.scrollXLoad || reactData.scrollYLoad) ? 30 : 0)
        })
      },
      /**
       * 如果有滚动条，则滚动到对应的行
       */
      scrollToRow (rowOrRowid, fieldOrColumn, options) {
        const { isAllOverflow, scrollYLoad, scrollXLoad } = reactData
        const { fullAllDataRowIdData, _sToTime } = internalData
        const rest = []
        let row: any
        if (XEUtils.isString(rowOrRowid) || XEUtils.isNumber(rowOrRowid)) {
          const rowid = rowOrRowid
          const rowRest = rowid ? fullAllDataRowIdData[rowid] : null
          if (rowRest) {
            row = rowRest.row
          }
        } else {
          row = rowOrRowid
        }
        if (row) {
          if (props.treeConfig) {
            rest.push($xeTable.scrollToTreeRow(row))
          } else {
            rest.push(rowToVisible($xeTable, row))
          }
        }
        if (fieldOrColumn) {
          rest.push(handleScrollToRowColumn(fieldOrColumn, row, options))
        }
        if (_sToTime) {
          clearTimeout(_sToTime)
        }
        return Promise.all(rest).then(() => {
          if (row) {
            if (!isAllOverflow && (scrollYLoad || scrollXLoad)) {
              calcCellHeight()
              calcCellWidth()
              // 可视区被渲染后位置被偏移
              internalData._sToTime = setTimeout(() => {
                internalData._sToTime = undefined
                if (scrollYLoad) {
                  if (props.treeConfig) {
                    $xeTable.scrollToTreeRow(row)
                  } else {
                    rowToVisible($xeTable, row)
                  }
                }
                if (scrollXLoad && fieldOrColumn) {
                  handleScrollToRowColumn(fieldOrColumn, row, options)
                }
              }, 350)
            }
            return nextTick()
          }
        })
      },
      /**
       * 如果有滚动条，则滚动到第一行
       */
      scrollToStartRow (fieldOrColumn, options) {
        const { afterFullData } = internalData
        return $xeTable.scrollToRow(afterFullData[0], fieldOrColumn, options)
      },
      /**
       * 如果有滚动条，则滚动到最后一行
       */
      scrollToEndRow (fieldOrColumn, options) {
        const { afterFullData } = internalData
        return $xeTable.scrollToRow(afterFullData[afterFullData.length - 1], fieldOrColumn, options)
      },
      /**
       * 如果有滚动条，则滚动到对应的列
       */
      scrollToColumn (fieldOrColumn, options) {
        return handleScrollToRowColumn(fieldOrColumn, null, options)
      },
      /**
       * 如果有滚动条，则滚动到第一列
       */
      scrollToStartColumn (options) {
        const { visibleColumn } = internalData
        return $xeTable.scrollToColumn(visibleColumn[0], options)
      },
      /**
       * 如果有滚动条，则滚动到最后一列
       */
      scrollToEndColumn (options) {
        const { visibleColumn } = internalData
        return $xeTable.scrollToColumn(visibleColumn[visibleColumn.length - 1], options)
      },
      /**
       * 手动清除滚动相关信息，还原到初始状态
       */
      clearScroll () {
        const { elemStore, scrollXStore, scrollYStore } = internalData
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value

        internalData.intoRunScroll = true

        setScrollLeft(xHandleEl, 0)
        setScrollLeft(bodyScrollElem, 0)
        setScrollLeft(headerScrollElem, 0)
        setScrollLeft(footerScrollElem, 0)

        setScrollTop(yHandleEl, 0)
        setScrollTop(bodyScrollElem, 0)
        setScrollTop(leftScrollElem, 0)
        setScrollTop(rightScrollElem, 0)

        scrollXStore.startIndex = 0
        scrollXStore.visibleStartIndex = 0
        scrollXStore.endIndex = scrollXStore.visibleSize
        scrollXStore.visibleEndIndex = scrollXStore.visibleSize
        scrollYStore.startIndex = 0
        scrollYStore.visibleStartIndex = 0
        scrollYStore.endIndex = scrollYStore.visibleSize
        scrollYStore.visibleEndIndex = scrollYStore.visibleSize

        return nextTick().then(() => {
          internalData.lastScrollLeft = 0
          internalData.lastScrollTop = 0
          internalData.intoRunScroll = false
        })
      },
      /**
       * 更新表尾合计
       */
      updateFooter () {
        const { showFooter, footerData, footerMethod } = props
        const { visibleColumn, afterFullData } = internalData
        let footData: any[] = []
        if (showFooter && footerData && footerData.length) {
          footData = footerData.slice(0)
        } else if (showFooter && footerMethod) {
          footData = visibleColumn.length ? footerMethod({ columns: visibleColumn, data: afterFullData, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }) : []
        }
        reactData.footerTableData = footData
        $xeTable.handleUpdateFooterMerge()
        $xeTable.dispatchEvent('footer-data-change', {
          visibleColumn: internalData.visibleColumn,
          footData
        }, null)
        return nextTick()
      },
      /**
       * 更新列状态 updateStatus({ row, column }, cellValue)
       * 如果组件值 v-model 发生 change 时，调用改函数用于更新某一列编辑状态
       * 如果单元格配置了校验规则，则会进行校验
       */
      updateStatus (slotParams, cellValue) {
        return nextTick().then(() => {
          const { editRules } = props
          const { isActivated } = internalData
          if (isActivated && slotParams && editRules) {
            return $xeTable.handleCellRuleUpdateStatus('change', slotParams, cellValue)
          }
        })
      },
      /**
       * 设置合并单元格 [{ row: Row|number, column: ColumnInfo|number, rowspan: number, colspan: number }]
       */
      setMergeCells (merges) {
        if (props.spanMethod) {
          errLog('vxe.error.errConflicts', ['merge-cells', 'span-method'])
        }
        handleBodyMerge(merges)
        $xeTable.handleUpdateBodyMerge()
        return nextTick().then(() => {
          const { expandColumn } = reactData
          const { mergeBodyList } = internalData
          if (expandColumn && mergeBodyList.length) {
            warnLog('vxe.error.errConflicts', ['type=expand', 'merge-cells | span-method'])
          }
          $xeTable.updateCellAreas()
          return updateStyle()
        })
      },
      /**
       * 移除单元格合并 [{row:Row|number, col:ColumnInfo|number}]
       */
      removeMergeCells (merges) {
        if (props.spanMethod) {
          errLog('vxe.error.errConflicts', ['merge-cells', 'span-method'])
        }
        const rest = removeBodyMerges(merges)
        $xeTable.handleUpdateBodyMerge()
        return nextTick().then(() => {
          $xeTable.updateCellAreas()
          updateStyle()
          return rest
        })
      },
      /**
       * 获取所有被合并的单元格
       */
      getMergeCells () {
        return internalData.mergeBodyList.slice(0)
      },
      /**
       * 清除所有单元格合并
       */
      clearMergeCells () {
        internalData.mergeBodyList = []
        internalData.mergeBodyMaps = {}
        internalData.mergeBodyCellMaps = {}
        internalData.mergeBodyRowMaps = {}
        internalData.mergeBodyColMaps = {}
        reactData.mergeBodyFlag++
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      setMergeHeaderCells (merges) {
        handleHeaderMerge(merges)
        $xeTable.handleUpdateHeaderMerge()
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      /**
       * 移除表头单元格合并 [{row:Row|number, col:ColumnInfo|number}]
       */
      removeMergeHeaderCells (merges) {
        const rest = removeHeaderMerges(merges)
        $xeTable.handleUpdateHeaderMerge()
        return nextTick().then(() => {
          updateStyle()
          return rest
        })
      },
      /**
       * 获取所有被合并的表头单元格
       */
      getMergeHeaderCells () {
        return internalData.mergeHeaderList.slice(0)
      },
      /**
       * 清除所有表头单元格合并
       */
      clearMergeHeaderCells () {
        internalData.mergeHeaderList = []
        internalData.mergeHeaderMaps = {}
        internalData.mergeHeaderCellMaps = {}
        internalData.mergeHeaderRowMaps = {}
        internalData.mergeBodyColMaps = {}
        reactData.mergeHeadFlag++
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      setMergeFooterCells (merges) {
        if (props.footerSpanMethod) {
          errLog('vxe.error.errConflicts', ['merge-footer-cells | merge-footer-items', 'footer-span-method'])
        }
        handleFooterMerge(merges)
        $xeTable.handleUpdateFooterMerge()
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      setMergeFooterItems (merges) {
        // errLog('vxe.error.delFunc', ['setMergeFooterItems', 'setMergeFooterCells'])
        return $xeTable.setMergeFooterCells(merges)
      },
      removeMergeFooterCells (merges) {
        if (props.footerSpanMethod) {
          errLog('vxe.error.errConflicts', ['merge-footer-cells | merge-footer-items', 'footer-span-method'])
        }
        const rest = removeFooterMerges(merges)
        $xeTable.handleUpdateFooterMerge()
        return nextTick().then(() => {
          updateStyle()
          return rest
        })
      },
      removeMergeFooterItems (merges) {
        // errLog('vxe.error.delFunc', ['removeMergeFooterItems', 'removeMergeFooterCells'])
        return $xeTable.removeMergeFooterCells(merges)
      },
      /**
       * 获取所有被合并的表尾
       */
      getMergeFooterCells () {
        return internalData.mergeFooterList.slice(0)
      },
      getMergeFooterItems () {
        // errLog('vxe.error.delFunc', ['getMergeFooterItems', 'getMergeFooterCells'])
        return $xeTable.getMergeFooterCells()
      },
      /**
       * 清除所有表尾合并
       */
      clearMergeFooterCells () {
        internalData.mergeFooterList = []
        internalData.mergeFooterMaps = {}
        internalData.mergeFooterCellMaps = {}
        internalData.mergeFooterRowMaps = {}
        internalData.mergeFooterColMaps = {}
        reactData.mergeFootFlag++
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      clearMergeFooterItems () {
        // errLog('vxe.error.delFunc', ['clearMergeFooterItems', 'clearMergeFooterCells'])
        return $xeTable.clearMergeFooterCells()
      },
      updateCellAreas () {
        const { mouseConfig } = props
        const mouseOpts = computeMouseOpts.value
        if (mouseConfig && mouseOpts.area && $xeTable.handleRecalculateCellAreaEvent) {
          return $xeTable.handleRecalculateCellAreaEvent()
        }
        return nextTick()
      },
      getCustomStoreData () {
        const { id } = props
        const customOpts = computeCustomOpts.value
        const { isRowGroupStatus, rowGroupList } = reactData
        const { fullColumnFieldData, collectColumn } = internalData
        const { storage, checkMethod, storeOptions } = customOpts
        const isAllCustom = storage === true
        const storageOpts: VxeTableDefines.VxeTableCustomStorageObj = Object.assign({}, isAllCustom ? {} : storage || {}, storeOptions)
        const isCustomResizable = hangleStorageDefaultValue(storageOpts.resizable, isAllCustom)
        const isCustomVisible = hangleStorageDefaultValue(storageOpts.visible, isAllCustom)
        const isCustomFixed = hangleStorageDefaultValue(storageOpts.fixed, isAllCustom)
        const isCustomSort = hangleStorageDefaultValue(storageOpts.sort, isAllCustom)
        const isCustomAggGroup = hangleStorageDefaultValue(storageOpts.aggGroup, isAllCustom)
        const isCustomAggFunc = hangleStorageDefaultValue(storageOpts.aggFunc, isAllCustom)
        const resizableData: Record<string, number> = {}
        const sortData: VxeTableDefines.CustomSortStoreObj[] = []
        const visibleData: Record<string, boolean> = {}
        const fixedData: Record<string, VxeColumnPropTypes.Fixed> = {}
        const aggGroupData: Record<string, boolean> = {}
        const aggFuncData: Record<string, VxeColumnPropTypes.AggFunc> = {}
        const storeData: VxeTableDefines.CustomStoreData = {
          resizableData: undefined,
          sortData: undefined,
          visibleData: undefined,
          fixedData: undefined,
          aggGroupData: undefined,
          aggFuncData: undefined
        }
        if (!id) {
          if (storage) {
            errLog('vxe.error.reqProp', ['id'])
          }
          return storeData
        }
        let hasResizable = 0
        let hasSort = 0
        let hasFixed = 0
        let hasVisible = 0
        let hasAggFunc = 0
        const sortMaps: Record<string, VxeTableDefines.CustomSortStoreObj> = {}
        XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
          const colKey = column.getKey()
          if (!colKey) {
            errLog('vxe.error.reqProp', [`${column.getTitle() || column.type || ''} -> column.field=?`])
            return
          }
          if (parentColumn) {
            if (isCustomSort) {
              const pColKey = parentColumn.getKey()
              const psObj = sortMaps[pColKey]
              hasSort = 1
              if (psObj) {
                const sObj = { k: colKey }
                sortMaps[colKey] = sObj
                if (!psObj.c) {
                  psObj.c = []
                }
                psObj.c.push(sObj)
              }
            }
          } else {
            if (isCustomSort) {
              hasSort = 1
              const sObj = { k: colKey }
              sortMaps[colKey] = sObj
              sortData.push(sObj)
            }
            // 只支持一级
            if (isCustomFixed && (column.fixed || '') !== (column.defaultFixed || '')) {
              hasFixed = 1
              fixedData[colKey] = column.fixed
            }
          }
          if (isCustomResizable && column.resizeWidth) {
            hasResizable = 1
            resizableData[colKey] = column.renderWidth
          }
          if (isCustomVisible && (!checkMethod || checkMethod({ $table: $xeTable, column }))) {
            if (!column.visible && column.defaultVisible) {
              hasVisible = 1
              visibleData[colKey] = false
            } else if (column.visible && !column.defaultVisible) {
              hasVisible = 1
              visibleData[colKey] = true
            }
          }
          if (isCustomAggFunc && (column.aggFunc || '') !== (column.defaultAggFunc || '')) {
            hasAggFunc = 1
            aggFuncData[colKey] = column.aggFunc
          }
        })
        if (hasResizable) {
          storeData.resizableData = resizableData
        }
        if (hasSort) {
          storeData.sortData = sortData
        }
        if (hasFixed) {
          storeData.fixedData = fixedData
        }
        if (hasVisible) {
          storeData.visibleData = visibleData
        }
        if (isCustomAggGroup && isRowGroupStatus) {
          rowGroupList.forEach(aggConf => {
            const colRest = fullColumnFieldData[aggConf.field]
            if (colRest) {
              aggGroupData[colRest.column.getKey()] = true
            }
          })
          storeData.aggGroupData = aggGroupData
        }
        if (hasAggFunc) {
          storeData.aggFuncData = aggFuncData
        }
        return storeData
      },
      focus () {
        internalData.isActivated = true
        return nextTick()
      },
      blur () {
        internalData.isActivated = false
        return nextTick()
      },
      /**
       * 已废弃，被 connectToolbar 替换
       * @deprecated
       */
      connect ($toolbar) {
        return $xeTable.connectToolbar($toolbar)
      },
      /**
       * 连接工具栏
       * @param $toolbar
       */
      connectToolbar ($toolbar) {
        if ($toolbar) {
          $xeToolbar = $toolbar
          $xeToolbar.syncUpdate({ collectColumn: internalData.collectColumn, $table: $xeTable })
        } else {
          errLog('vxe.error.barUnableLink')
        }
        return nextTick()
      }
    }

    /**
     * 全局按下事件处理
     */
    const handleGlobalMousedownEvent = (evnt: MouseEvent) => {
      const { editStore, ctxMenuStore, customStore } = reactData
      const { mouseConfig, editRules } = props
      const el = refElem.value
      const editOpts = computeEditOpts.value
      const validOpts = computeValidOpts.value
      const areaOpts = computeAreaOpts.value
      const { actived } = editStore
      const $validTooltip = refValidTooltip.value
      const tableFilter = refTableFilter.value
      const tableCustom = refTableCustom.value
      const tableMenu = refTableMenu.value
      // 筛选
      if (tableFilter) {
        if (getEventTargetNode(evnt, el, 'vxe-cell--filter').flag) {
          // 如果点击了筛选按钮
        } else if (getEventTargetNode(evnt, tableFilter.getRefMaps().refElem.value).flag) {
          // 如果点击筛选容器
        } else {
          if (!getEventTargetNode(evnt, document.body, 'vxe-table--ignore-clear').flag) {
            $xeTable.preventEvent(evnt, 'event.clearFilter', internalData._currFilterParams, $xeTable.closeFilter)
          }
        }
      }
      // 自定义列
      if (tableCustom) {
        if (customStore.btnEl === evnt.target || getEventTargetNode(evnt, document.body, 'vxe-toolbar-custom-target').flag) {
          // 如果点击了自定义列按钮
        } else if (getEventTargetNode(evnt, tableCustom.getRefMaps().refElem.value).flag) {
          // 如果点击自定义列容器
        } else {
          if (!getEventTargetNode(evnt, document.body, 'vxe-table--ignore-clear').flag) {
            if (customStore.visible && $xeTable.closeCustom) {
              $xeTable.preventEvent(evnt, 'event.clearCustom', {}, () => {
                $xeTable.closeCustom()
                $xeTable.dispatchEvent('custom', { type: 'close' }, evnt)
              })
            }
          }
        }
      }

      // 如果已激活了编辑状态
      if (actived.row) {
        if (!(editOpts.autoClear === false)) {
          // 如果是激活状态，点击了单元格之外
          const cell = actived.args.cell
          if ((!cell || !getEventTargetNode(evnt, cell).flag)) {
            if ($validTooltip && getEventTargetNode(evnt, $validTooltip.$el as HTMLDivElement).flag) {
              // 如果是激活状态，且点击了校验提示框
            } else if (!internalData._lastCallTime || internalData._lastCallTime + 50 < Date.now()) {
              // 如果是激活状态，点击了单元格之外
              if (!getEventTargetNode(evnt, document.body, 'vxe-table--ignore-clear').flag) {
                // 如果手动调用了激活单元格，避免触发源被移除后导致重复关闭
                $xeTable.preventEvent(evnt, 'event.clearEdit', actived.args, () => {
                  let isClear
                  if (editOpts.mode === 'row') {
                    const rowTargetNode = getEventTargetNode(evnt, el, 'vxe-body--row')
                    const rowNodeRest = rowTargetNode.flag ? $xeTable.getRowNode(rowTargetNode.targetElem) : null
                    // row 方式，如果点击了不同行
                    isClear = rowNodeRest ? !$xeTable.eqRow(rowNodeRest.item, actived.args.row) : false
                  } else {
                    // cell 方式，如果是非编辑列
                    isClear = !getEventTargetNode(evnt, el, 'col--edit').flag
                  }
                  // 如果点击表头行，则清除激活状态
                  if (!isClear) {
                    isClear = getEventTargetNode(evnt, el, 'vxe-header--row').flag
                  }
                  // 如果点击表尾行，则清除激活状态
                  if (!isClear) {
                    isClear = getEventTargetNode(evnt, el, 'vxe-footer--row').flag
                  }
                  // 如果固定了高度且点击了行之外的空白处，则清除激活状态
                  if (!isClear && props.height && !reactData.overflowY) {
                    const bodyWrapperElem = evnt.target as HTMLDivElement
                    if (hasClass(bodyWrapperElem, 'vxe-table--body-wrapper')) {
                      isClear = evnt.offsetY < bodyWrapperElem.clientHeight
                    }
                  }
                  if (
                    isClear ||
                      // 如果点击了当前表格之外
                      !getEventTargetNode(evnt, el).flag
                  ) {
                    setTimeout(() => {
                      $xeTable.handleClearEdit(evnt).then(() => {
                        // 如果存在校验，点击了表格之外则清除
                        if (!internalData.isActivated && editRules && validOpts.autoClear) {
                          reactData.validErrorMaps = {}
                        }
                      })
                    })
                  }
                })
              }
            }
          }
        }
      } else if (mouseConfig) {
        if (!getEventTargetNode(evnt, el).flag && !($xeGGWrapper && getEventTargetNode(evnt, $xeGGWrapper.getRefMaps().refElem.value).flag) && !(tableMenu && getEventTargetNode(evnt, tableMenu.getRefMaps().refElem.value).flag) && !($xeToolbar && getEventTargetNode(evnt, $xeToolbar.getRefMaps().refElem.value).flag)) {
          if ($xeTable.clearSelected) {
            $xeTable.clearSelected()
          }
          if (areaOpts.autoClear) {
            if ($xeTable.getCellAreas) {
              const cellAreas = $xeTable.getCellAreas()
              if (cellAreas && cellAreas.length && !getEventTargetNode(evnt, document.body, 'vxe-table--ignore-areas-clear').flag) {
                $xeTable.preventEvent(evnt, 'event.clearAreas', {}, () => {
                  $xeTable.clearCellAreas()
                  $xeTable.clearCopyCellArea()
                  dispatchEvent('clear-cell-area-selection', { cellAreas }, evnt)
                })
              }
            }
          }
        }
      }
      // 如果配置了快捷菜单且，点击了其他地方则关闭
      if ($xeTable.closeMenu) {
        if (ctxMenuStore.visible && tableMenu && !getEventTargetNode(evnt, tableMenu.getRefMaps().refElem.value).flag) {
          $xeTable.closeMenu()
        }
      }
      if (!(actived.row && getEventTargetNode(evnt, document.body, 'vxe-table--ignore-clear').flag)) {
        const isActivated = getEventTargetNode(evnt, $xeGGWrapper ? $xeGGWrapper.getRefMaps().refElem.value : el).flag
        // 如果存在校验，点击了表格之外则清除
        if (!isActivated && editRules && validOpts.autoClear) {
          reactData.validErrorMaps = {}
        }
        // 最后激活的表格
        internalData.isActivated = isActivated
      }
    }

    /**
     * 窗口失焦事件处理
     */
    const handleGlobalBlurEvent = () => {
      $xeTable.closeFilter()
      if ($xeTable.closeMenu) {
        $xeTable.closeMenu()
      }
    }

    const reUpdateCustomStyleEvent = (evnt: MouseEvent) => {
      const { customStore } = reactData
      const customOpts = computeCustomOpts.value
      const { popupOptions } = customOpts
      const { transfer } = popupOptions || {}
      if (transfer && customStore.visible) {
        const tableCustom = refTableCustom.value
        if (tableCustom && $xeTable.handleCustomStyle && !checkTargetElement(evnt.target, [tableCustom.getRefMaps().refElem.value as HTMLDivElement], evnt.currentTarget)) {
          $xeTable.handleCustomStyle().then(() => {
            $xeTable.handleCustomStyle()
          })
        }
      }
    }

    const handleGlobalScrollEvent = (evnt: MouseEvent) => {
      reUpdateCustomStyleEvent(evnt)
    }

    /**
     * 全局滚动事件
     */
    const handleGlobalMousewheelEvent = () => {
      $xeTable.closeTooltip()
      if ($xeTable.closeMenu) {
        $xeTable.closeMenu()
      }
    }

    /**
     * 表格键盘事件
     */
    const keydownEvent = (evnt: KeyboardEvent) => {
      const { mouseConfig, keyboardConfig } = props
      const { filterStore, ctxMenuStore, editStore } = reactData
      const mouseOpts = computeMouseOpts.value
      const keyboardOpts = computeKeyboardOpts.value
      const { actived } = editStore
      const isEsc = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ESCAPE)
      if (isEsc) {
        $xeTable.preventEvent(evnt, 'event.keydown', null, () => {
          dispatchEvent('keydown-start', {}, evnt)
          if (keyboardConfig && mouseConfig && mouseOpts.area && $xeTable.handleKeyboardCellAreaEvent) {
            $xeTable.handleKeyboardCellAreaEvent(evnt)
          } else if (actived.row || filterStore.visible || ctxMenuStore.visible) {
            evnt.stopPropagation()
            // 如果按下了 Esc 键，关闭快捷菜单、筛选
            if ($xeTable.closeMenu) {
              $xeTable.closeMenu()
            }
            $xeTable.closeFilter()
            if (keyboardConfig && keyboardOpts.isEsc) {
              // 如果是激活编辑状态，则取消编辑
              if (actived.row) {
                const params = actived.args
                $xeTable.handleClearEdit(evnt)
                // 如果配置了选中功能，则为选中状态
                if (mouseOpts.selected) {
                  nextTick(() => $xeTable.handleSelected(params, evnt))
                }
              }
            }
          }
          dispatchEvent('keydown', {}, evnt)
          dispatchEvent('keydown-end', {}, evnt)
        })
      }
    }

    const contextMenuEvent = (evnt: MouseEvent) => {
      dispatchEvent('context-menu', {}, evnt)
    }

    /**
     * 全局键盘事件
     */
    const handleGlobalKeydownEvent = (evnt: KeyboardEvent) => {
      // 该行为只对当前激活的表格有效
      if (internalData.isActivated) {
        $xeTable.preventEvent(evnt, 'event.keydown', null, () => {
          const { mouseConfig, keyboardConfig, treeConfig, editConfig, highlightCurrentRow, highlightCurrentColumn } = props
          const { ctxMenuStore, editStore } = reactData
          const { afterFullData, visibleColumn, currentRow } = internalData
          const isContentMenu = computeIsContentMenu.value
          const bodyMenu = computeBodyMenu.value
          const keyboardOpts = computeKeyboardOpts.value
          const mouseOpts = computeMouseOpts.value
          const editOpts = computeEditOpts.value
          const treeOpts = computeTreeOpts.value
          const menuList = computeMenuList.value
          const rowOpts = computeRowOpts.value
          const columnOpts = computeColumnOpts.value
          const { isLastEnterAppendRow, beforeEnterMethod, enterMethod, isLastTabAppendRow, beforeTabMethod, tabMethod } = keyboardOpts
          const { selected, actived } = editStore
          const childrenField = treeOpts.children || treeOpts.childrenField
          const keyCode = evnt.keyCode
          const isEsc = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ESCAPE)
          const hasBackspaceKey = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.BACKSPACE)
          const isTab = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.TAB)
          const isEnter = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ENTER)
          const isSpacebar = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.SPACEBAR)
          const isLeftArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_LEFT)
          const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)
          const isRightArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_RIGHT)
          const isDwArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)
          const hasDeleteKey = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.DELETE)
          const isF2 = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.F2)
          const isContextMenu = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.CONTEXT_MENU)
          const isControlKey = hasControlKey(evnt)
          const hasShiftKey = evnt.shiftKey
          const isAltKey = evnt.altKey
          const operArrow = isLeftArrow || isUpArrow || isRightArrow || isDwArrow
          const operCtxMenu = isContentMenu && ctxMenuStore.visible && (isEnter || isSpacebar || operArrow)
          const isEditStatus = isEnableConf(editConfig) && actived.column && actived.row
          const beforeEditMethod = editOpts.beforeEditMethod || editOpts.activeMethod
          if (operCtxMenu) {
            // 如果配置了右键菜单; 支持方向键操作、回车
            evnt.preventDefault()
            if (ctxMenuStore.showChild && hasChildrenList(ctxMenuStore.selected)) {
              $xeTable.moveCtxMenu(evnt, ctxMenuStore, 'selectChild', isLeftArrow, false, ctxMenuStore.selected.children)
            } else {
              $xeTable.moveCtxMenu(evnt, ctxMenuStore, 'selected', isRightArrow, true, menuList)
            }
          } else if (keyboardConfig && mouseConfig && mouseOpts.area && $xeTable.handleKeyboardCellAreaEvent) {
            $xeTable.handleKeyboardCellAreaEvent(evnt)
          } else if (isEsc) {
            // 如果按下了 Esc 键，关闭快捷菜单、筛选
            if ($xeTable.closeMenu) {
              $xeTable.closeMenu()
            }
            $xeTable.closeFilter()
            if (keyboardConfig && keyboardOpts.isEsc) {
              // 如果是激活编辑状态，则取消编辑
              if (actived.row) {
                const params = actived.args
                $xeTable.handleClearEdit(evnt)
                // 如果配置了选中功能，则为选中状态
                if (mouseOpts.selected) {
                  nextTick(() => $xeTable.handleSelected(params, evnt))
                }
              }
            }
          } else if (isSpacebar && keyboardConfig && keyboardOpts.isChecked && selected.row && selected.column && (selected.column.type === 'checkbox' || selected.column.type === 'radio')) {
            // 空格键支持选中复选框
            evnt.preventDefault()
            if (selected.column.type === 'checkbox') {
              $xeTable.handleToggleCheckRowEvent(evnt, selected.args)
            } else {
              $xeTable.triggerRadioRowEvent(evnt, selected.args)
            }
          } else if (isF2 && isEnableConf(editConfig)) {
            if (!isEditStatus) {
              // 如果按下了 F2 键
              if (selected.row && selected.column) {
                evnt.preventDefault()
                $xeTable.handleEdit(selected.args, evnt)
              }
            }
          } else if (isContextMenu) {
            // 如果按下上下文键
            internalData._keyCtx = selected.row && selected.column && bodyMenu.length
            clearTimeout(internalData.keyCtxTimeout)
            internalData.keyCtxTimeout = setTimeout(() => {
              internalData._keyCtx = false
            }, 1000)
          } else if (isEnter && !isAltKey && keyboardConfig && keyboardOpts.isEnter && (selected.row || actived.row || (treeConfig && (rowOpts.isCurrent || highlightCurrentRow) && currentRow))) {
            // 退出选中
            if (isControlKey) {
              // 如果是激活编辑状态，则取消编辑
              if (actived.row) {
                const params = actived.args
                $xeTable.handleClearEdit(evnt)
                // 如果配置了选中功能，则为选中状态
                if (mouseOpts.selected) {
                  nextTick(() => {
                    $xeTable.handleSelected(params, evnt)
                  })
                }
              }
            } else {
              // 如果是激活状态，退则出到上一行/下一行
              if (selected.row || actived.row) {
                const activeRow = selected.row || actived.row
                const activeColumn = selected.column || actived.column
                const activeParams = selected.row ? selected.args : actived.args
                if (hasShiftKey) {
                  if (keyboardOpts.enterToTab) {
                    $xeTable.moveTabSelected(activeParams, hasShiftKey, evnt)
                  } else {
                    $xeTable.moveEnterSelected(activeParams, isLeftArrow, true, isRightArrow, false, evnt)
                  }
                } else {
                  const _rowIndex = $xeTable.getVTRowIndex(activeRow)
                  const _columnIndex = $xeTable.getVTColumnIndex(activeColumn)
                  if (keyboardOpts.enterToTab) {
                    const ttrParams = {
                      row: activeRow,
                      rowIndex: $xeTable.getRowIndex(activeRow),
                      $rowIndex: $xeTable.getVMRowIndex(activeRow),
                      _rowIndex,
                      column: activeColumn,
                      columnIndex: $xeTable.getColumnIndex(activeColumn),
                      $columnIndex: $xeTable.getVMColumnIndex(activeColumn),
                      _columnIndex,
                      $table: $xeTable
                    }
                    if (!beforeTabMethod || beforeTabMethod(ttrParams) !== false) {
                      evnt.preventDefault()
                      // 最后一行按下Tab键，自动追加一行
                      if (isLastTabAppendRow) {
                        const newColumn = visibleColumn[0]
                        if (_rowIndex >= afterFullData.length - 1 && _columnIndex >= visibleColumn.length - 1) {
                          if (actived.row) {
                            $xeTable.handleClearEdit(evnt)
                          }
                          $xeTable.insertAt({}, -1).then(({ row: newRow }) => {
                            $xeTable.scrollToRow(newRow, newColumn)
                            $xeTable.handleSelected({ ...activeParams, row: newRow, column: newColumn }, evnt)
                          })
                          $xeTable.dispatchEvent('tab-append-row', ttrParams, evnt)
                          return
                        }
                      }
                      if (tabMethod) {
                        tabMethod(ttrParams)
                      } else {
                        $xeTable.moveTabSelected(activeParams, hasShiftKey, evnt)
                      }
                    }
                  } else {
                    const etrParams = {
                      row: activeRow,
                      rowIndex: $xeTable.getRowIndex(activeRow),
                      $rowIndex: $xeTable.getVMRowIndex(activeRow),
                      _rowIndex,
                      column: activeColumn,
                      columnIndex: $xeTable.getColumnIndex(activeColumn),
                      $columnIndex: $xeTable.getVMColumnIndex(activeColumn),
                      _columnIndex,
                      $table: $xeTable
                    }
                    if (!beforeEnterMethod || beforeEnterMethod(etrParams) !== false) {
                      evnt.preventDefault()
                      // 最后一行按下回车键，自动追加一行
                      if (isLastEnterAppendRow) {
                        if (_rowIndex >= afterFullData.length - 1) {
                          $xeTable.insertAt({}, -1).then(({ row: newRow }) => {
                            $xeTable.scrollToRow(newRow, activeColumn)
                            $xeTable.handleSelected({ ...activeParams, row: newRow }, evnt)
                          })
                          $xeTable.dispatchEvent('enter-append-row', etrParams, evnt)
                          return
                        }
                      }
                      if (enterMethod) {
                        enterMethod(etrParams)
                      } else {
                        if (actived.row) {
                          $xeTable.handleClearEdit(evnt)
                        }
                        $xeTable.moveEnterSelected(activeParams, isLeftArrow, false, isRightArrow, true, evnt)
                      }
                    }
                  }
                }
              } else if (treeConfig && (rowOpts.isCurrent || highlightCurrentRow) && currentRow) {
                // 如果是树形表格当前行回车移动到子节点
                const childrens = currentRow[childrenField]
                if (childrens && childrens.length) {
                  evnt.preventDefault()
                  const targetRow = childrens[0]
                  const params = {
                    $table: $xeTable,
                    row: targetRow,
                    rowIndex: $xeTable.getRowIndex(targetRow),
                    $rowIndex: $xeTable.getVMRowIndex(targetRow)
                  }
                  $xeTable.setTreeExpand(currentRow, true)
                    .then(() => $xeTable.scrollToRow(targetRow))
                    .then(() => $xeTable.triggerCurrentRowEvent(evnt, params))
                }
              }
            }
          } else if (operArrow && keyboardConfig && keyboardOpts.isArrow) {
            if (!isEditStatus) {
              // 如果按下了方向键
              if (mouseOpts.selected && selected.row && selected.column) {
                $xeTable.moveArrowSelected(selected.args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow, evnt)
              } else {
                // 当前行按键上下移动
                if ((isUpArrow || isDwArrow) && (rowOpts.isCurrent || highlightCurrentRow)) {
                  $xeTable.moveCurrentRow(isUpArrow, isDwArrow, evnt)
                }
                // 当前行按键左右移动
                if ((isLeftArrow || isRightArrow) && (columnOpts.isCurrent || highlightCurrentColumn)) {
                  $xeTable.moveCurrentColumn(isLeftArrow, isRightArrow, evnt)
                }
              }
            }
          } else if (isTab && keyboardConfig && keyboardOpts.isTab) {
            // 如果按下了 Tab 键切换
            if (selected.row || actived.row) {
              const activeRow = selected.row || actived.row
              const activeColumn = selected.column || actived.column
              const activeParams = selected.row ? selected.args : actived.args
              const _rowIndex = $xeTable.getVTRowIndex(activeRow)
              const _columnIndex = $xeTable.getVTColumnIndex(activeColumn)
              const ttrParams = {
                row: activeRow,
                rowIndex: $xeTable.getRowIndex(activeRow),
                $rowIndex: $xeTable.getVMRowIndex(activeRow),
                _rowIndex,
                column: activeColumn,
                columnIndex: $xeTable.getColumnIndex(activeColumn),
                $columnIndex: $xeTable.getVMColumnIndex(activeColumn),
                _columnIndex,
                $table: $xeTable
              }
              if (!beforeTabMethod || beforeTabMethod(ttrParams) !== false) {
                evnt.preventDefault()
                // 最后一行按下Tab键，自动追加一行
                if (isLastTabAppendRow) {
                  const newColumn = visibleColumn[0]
                  if (_rowIndex >= afterFullData.length - 1 && _columnIndex >= visibleColumn.length - 1) {
                    if (actived.row) {
                      $xeTable.handleClearEdit(evnt)
                    }
                    $xeTable.insertAt({}, -1).then(({ row: newRow }) => {
                      $xeTable.scrollToRow(newRow, newColumn)
                      $xeTable.handleSelected({ ...activeParams, row: newRow, column: newColumn }, evnt)
                    })
                    $xeTable.dispatchEvent('tab-append-row', ttrParams, evnt)
                    return
                  }
                }
                if (tabMethod) {
                  tabMethod(ttrParams)
                } else {
                  if (actived.row) {
                    $xeTable.handleClearEdit(evnt)
                  }
                  $xeTable.moveTabSelected(activeParams, hasShiftKey, evnt)
                }
              }
            }
          } else if (keyboardConfig && keyboardOpts.isDel && hasDeleteKey && isEnableConf(editConfig) && (selected.row || selected.column)) {
            // 如果是删除键
            if (!isEditStatus) {
              const { delMethod } = keyboardOpts
              const params = {
                row: selected.row,
                rowIndex: $xeTable.getRowIndex(selected.row),
                column: selected.column,
                columnIndex: $xeTable.getColumnIndex(selected.column),
                $table: $xeTable,
                $grid: $xeGrid,
                $gantt: $xeGantt
              }
              // 是否被禁用
              if (!beforeEditMethod || beforeEditMethod(params)) {
                if (delMethod) {
                  delMethod(params)
                } else {
                  setCellValue(selected.row, selected.column, null)
                }
                // 如果按下 del 键，更新表尾数据
                $xeTable.updateFooter()
                dispatchEvent('cell-delete-value', params, evnt)
              }
            }
          } else if (hasBackspaceKey && keyboardConfig && keyboardOpts.isBack && isEnableConf(editConfig) && (selected.row || selected.column)) {
            if (!isEditStatus) {
              const { backMethod } = keyboardOpts
              // 如果是删除键
              if (keyboardOpts.isDel && isEnableConf(editConfig) && (selected.row || selected.column)) {
                const params = {
                  row: selected.row,
                  rowIndex: $xeTable.getRowIndex(selected.row),
                  column: selected.column,
                  columnIndex: $xeTable.getColumnIndex(selected.column),
                  $table: $xeTable,
                  $grid: $xeGrid,
                  $gantt: $xeGantt
                }
                // 是否被禁用
                if (!beforeEditMethod || beforeEditMethod(params)) {
                  if (backMethod) {
                    backMethod(params)
                  } else {
                    setCellValue(selected.row, selected.column, null)
                    $xeTable.handleEdit(selected.args, evnt)
                  }
                  dispatchEvent('cell-backspace-value', params, evnt)
                }
              }
            }
          } else if (hasBackspaceKey && keyboardConfig && treeConfig && keyboardOpts.isBack && (rowOpts.isCurrent || highlightCurrentRow) && currentRow) {
            // 如果树形表格回退键关闭当前行返回父节点
            const { parent: parentRow } = XEUtils.findTree(internalData.afterTreeFullData, item => item === currentRow, { children: childrenField })
            if (parentRow) {
              evnt.preventDefault()
              const params = {
                row: parentRow,
                rowIndex: $xeTable.getRowIndex(parentRow),
                $rowIndex: $xeTable.getVMRowIndex(parentRow),
                $table: $xeTable,
                $grid: $xeGrid,
                $gantt: $xeGantt
              }
              $xeTable.setTreeExpand(parentRow, false)
                .then(() => $xeTable.scrollToRow(parentRow))
                .then(() => $xeTable.triggerCurrentRowEvent(evnt, params))
            }
          } else if (keyboardConfig && isEnableConf(editConfig) && keyboardOpts.isEdit && !isControlKey && (isSpacebar || (keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90) || (keyCode >= 96 && keyCode <= 111) || (keyCode >= 186 && keyCode <= 192) || (keyCode >= 219 && keyCode <= 222))) {
            const { editMode, editMethod } = keyboardOpts
            // 启用编辑后，空格键功能将失效
            // if (isSpacebar) {
            //   evnt.preventDefault()
            // }
            // 如果是按下非功能键之外允许直接编辑
            if (selected.column && selected.row && isEnableConf(selected.column.editRender)) {
              const beforeEditMethod = editOpts.beforeEditMethod || editOpts.activeMethod
              const params = {
                row: selected.row,
                rowIndex: $xeTable.getRowIndex(selected.row),
                column: selected.column,
                columnIndex: $xeTable.getColumnIndex(selected.column),
                $table: $xeTable,
                $grid: $xeGrid,
                $gantt: $xeGantt
              }
              if (!beforeEditMethod || beforeEditMethod({ ...selected.args, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt })) {
                if (editMethod) {
                  editMethod(params)
                } else {
                  // 追加方式与覆盖式
                  if (editMode !== 'insert') {
                    setCellValue(selected.row, selected.column, null)
                  }
                  $xeTable.handleEdit(selected.args, evnt)
                }
              }
            }
          }
          dispatchEvent('keydown', {}, evnt)
        })
      }
    }

    const handleGlobalPasteEvent = (evnt: ClipboardEvent) => {
      const { keyboardConfig, mouseConfig } = props
      const { editStore, filterStore } = reactData
      const { isActivated } = internalData
      const mouseOpts = computeMouseOpts.value
      const keyboardOpts = computeKeyboardOpts.value
      const { actived } = editStore
      if (isActivated && !filterStore.visible) {
        if (!(actived.row || actived.column)) {
          if (keyboardConfig && keyboardOpts.isClip && mouseConfig && mouseOpts.area && $xeTable.handlePeClAreaEvent) {
            $xeTable.handlePeClAreaEvent(evnt)
          }
        }
        dispatchEvent('paste', {}, evnt)
      }
    }

    const handleGlobalCopyEvent = (evnt: ClipboardEvent) => {
      const { keyboardConfig, mouseConfig } = props
      const { editStore, filterStore } = reactData
      const { isActivated } = internalData
      const mouseOpts = computeMouseOpts.value
      const keyboardOpts = computeKeyboardOpts.value
      const { actived } = editStore
      if (isActivated && !filterStore.visible) {
        if (!(actived.row || actived.column)) {
          if (keyboardConfig && keyboardOpts.isClip && mouseConfig && mouseOpts.area && $xeTable.handleCyClAreaEvent) {
            $xeTable.handleCyClAreaEvent(evnt)
          }
        }
        dispatchEvent('copy', {}, evnt)
      }
    }

    const handleGlobalCutEvent = (evnt: ClipboardEvent) => {
      const { keyboardConfig, mouseConfig } = props
      const { editStore, filterStore } = reactData
      const { isActivated } = internalData
      const mouseOpts = computeMouseOpts.value
      const keyboardOpts = computeKeyboardOpts.value
      const { actived } = editStore
      if (isActivated && !filterStore.visible) {
        if (!(actived.row || actived.column)) {
          if (keyboardConfig && keyboardOpts.isClip && mouseConfig && mouseOpts.area && $xeTable.handleCutCellAreaEvent) {
            $xeTable.handleCutCellAreaEvent(evnt)
          }
        }
        dispatchEvent('cut', {}, evnt)
      }
    }

    const handleGlobalResizeEvent = () => {
      if ($xeTable.closeMenu) {
        $xeTable.closeMenu()
      }
      const el = refElem.value
      if (!el || !el.clientWidth) {
        return
      }
      handleResizeEvent()
      $xeTable.updateCellAreas()
    }

    const handleTargetEnterEvent = (isClear: boolean) => {
      const $tooltip = refTooltip.value
      clearTimeout(internalData.tooltipTimeout)
      if (isClear) {
        $xeTable.closeTooltip()
      } else {
        if ($tooltip && $tooltip.setActived) {
          $tooltip.setActived(true)
        }
      }
    }

    const clearCrossTableDragStatus = () => {
      crossTableDragRowObj = null
      crossTableDragRowInfo.row = null
    }

    const clearDragStatus = () => {
      const { dragRow, dragCol } = reactData
      if (dragRow || dragCol) {
        clearColDropOrigin()
        clearRowDropOrigin()
        hideDropTip()
        clearCrossTableDragStatus()
        reactData.dragRow = null
        reactData.dragCol = null
      }
    }

    const handleRowDragEndClearStatus = () => {
      clearRowDragData()
      clearCrossTableDragStatus()
      handleRecalculateStyle(false, true, true)
    }

    const handleColDragEndClearStatus = () => {
      clearColDragData()
      clearCrossTableDragStatus()
      handleRecalculateStyle(true, true, true)
    }

    const clearRowDropOrigin = () => {
      const el = refElem.value
      if (el) {
        const clss = 'row--drag-origin'
        XEUtils.arrayEach(el.querySelectorAll(`.${clss}`), (elem) => {
          (elem as HTMLTableCellElement).draggable = false
          removeClass(elem, clss)
        })
      }
    }

    const updateRowDropOrigin = (row: any) => {
      const el = refElem.value
      if (el) {
        const clss = 'row--drag-origin'
        const rowid = getRowid($xeTable, row)
        XEUtils.arrayEach(el.querySelectorAll(`[rowid="${rowid}"]`), (elem) => {
          addClass(elem, clss)
        })
      }
    }

    const updateRowDropTipContent = (tdEl: HTMLElement) => {
      const { dragConfig } = props
      const { dragRow } = reactData
      const rowDragOpts = computeRowDragOpts.value
      const { tooltipMethod } = rowDragOpts
      const rTooltipMethod = tooltipMethod || (dragConfig ? dragConfig.rowTooltipMethod : null)
      let tipContent = ''
      if (rTooltipMethod) {
        const rtParams = {
          $table: $xeTable,
          row: dragRow
        }
        tipContent = `${rTooltipMethod(rtParams) || ''}`
      } else {
        tipContent = getI18n('vxe.table.dragTip', [tdEl.textContent || ''])
      }
      reactData.dragTipText = tipContent
    }

    const updateColDropOrigin = (column: VxeTableDefines.ColumnInfo) => {
      const el = refElem.value
      if (el) {
        const colQuerys: string[] = []
        XEUtils.eachTree([column], item => {
          colQuerys.push(`[colid="${item.id}"]`)
        })
        const clss = 'col--drag-origin'
        XEUtils.arrayEach(el.querySelectorAll(colQuerys.join(',')), (elem) => {
          addClass(elem, clss)
        })
      }
    }

    const clearColDropOrigin = () => {
      const el = refElem.value
      if (el) {
        const clss = 'col--drag-origin'
        XEUtils.arrayEach(el.querySelectorAll(`.${clss}`), (elem) => {
          (elem as HTMLTableCellElement).draggable = false
          removeClass(elem, clss)
        })
      }
    }

    const updateColDropTipContent = (tdEl: HTMLElement) => {
      const { dragCol } = reactData
      const columnDragOpts = computeColumnDragOpts.value
      const { tooltipMethod } = columnDragOpts
      let tipContent = ''
      if (tooltipMethod) {
        const dtParams = {
          $table: $xeTable,
          column: dragCol as VxeTableDefines.ColumnInfo
        }
        tipContent = `${tooltipMethod(dtParams) || ''}`
      } else {
        tipContent = getI18n('vxe.table.dragTip', [tdEl.textContent || ''])
      }
      reactData.dragTipText = tipContent
    }

    const showDropTip = (evnt: DragEvent | MouseEvent, trEl: HTMLElement | null, thEl: HTMLElement | null, showLine: boolean, dragPos: string) => {
      let wrapperEl = refElem.value
      if ($xeGantt && trEl) {
        const { refGanttContainerElem } = $xeGantt.getRefMaps()
        const ganttContainerElem = refGanttContainerElem.value
        if (ganttContainerElem) {
          wrapperEl = ganttContainerElem
        }
      }
      if (!wrapperEl) {
        return
      }
      const { overflowX, scrollbarWidth, overflowY, scrollbarHeight } = reactData
      const { prevDragToChild } = internalData
      const wrapperRect = wrapperEl.getBoundingClientRect()
      const osbWidth = overflowY ? scrollbarWidth : 0
      const osbHeight = overflowX ? scrollbarHeight : 0
      const tableWrapperWidth = wrapperEl.clientWidth
      const tableWrapperHeight = wrapperEl.clientHeight
      if (trEl) {
        const rdLineEl = refDragRowLineElem.value
        if (rdLineEl) {
          if (showLine) {
            const scrollbarYToLeft = computeScrollbarYToLeft.value
            const trRect = trEl.getBoundingClientRect()
            let trHeight = trEl.clientHeight
            const offsetTop = Math.max(1, trRect.y - wrapperRect.y)
            if (offsetTop + trHeight > tableWrapperHeight - osbHeight) {
              trHeight = tableWrapperHeight - offsetTop - osbHeight
            }
            rdLineEl.style.display = 'block'
            rdLineEl.style.left = `${scrollbarYToLeft ? osbWidth : 0}px`
            rdLineEl.style.top = `${offsetTop}px`
            rdLineEl.style.height = `${trHeight}px`
            rdLineEl.style.width = `${tableWrapperWidth - osbWidth}px`
            rdLineEl.setAttribute('drag-pos', dragPos)
            rdLineEl.setAttribute('drag-to-child', prevDragToChild ? 'y' : 'n')
          } else {
            rdLineEl.style.display = ''
          }
        }
      } else if (thEl) {
        const cdLineEl = refDragColLineElem.value
        if (cdLineEl) {
          if (showLine) {
            const scrollbarXToTop = computeScrollbarXToTop.value
            const leftContainerElem = refLeftContainer.value
            const leftContainerWidth = leftContainerElem ? leftContainerElem.clientWidth : 0
            const rightContainerElem = refRightContainer.value
            const rightContainerWidth = rightContainerElem ? rightContainerElem.clientWidth : 0
            const thRect = thEl.getBoundingClientRect()
            let thWidth = thEl.clientWidth
            const offsetTop = Math.max(0, thRect.y - wrapperRect.y)
            const startX = leftContainerWidth
            let offsetLeft = thRect.x - wrapperRect.x
            if (offsetLeft < startX) {
              thWidth -= startX - offsetLeft
              offsetLeft = startX
            }
            const endX = tableWrapperWidth - rightContainerWidth - (rightContainerWidth ? 0 : osbWidth)
            if (offsetLeft + thWidth > endX) {
              thWidth = endX - offsetLeft
            }
            cdLineEl.style.display = 'block'
            cdLineEl.style.top = `${offsetTop}px`
            cdLineEl.style.left = `${offsetLeft}px`
            cdLineEl.style.width = `${thWidth}px`
            if (prevDragToChild) {
              cdLineEl.style.height = `${thRect.height}px`
            } else {
              cdLineEl.style.height = `${tableWrapperHeight - offsetTop - (scrollbarXToTop ? 0 : osbHeight)}px`
            }
            cdLineEl.setAttribute('drag-pos', dragPos)
            cdLineEl.setAttribute('drag-to-child', prevDragToChild ? 'y' : 'n')
          } else {
            cdLineEl.style.display = ''
          }
        }
      }
      const rdTipEl = refDragTipElem.value
      if (rdTipEl) {
        rdTipEl.style.display = 'block'
        rdTipEl.style.top = `${Math.min(wrapperEl.clientHeight - wrapperEl.scrollTop - rdTipEl.clientHeight, evnt.clientY - wrapperRect.y)}px`
        rdTipEl.style.left = `${Math.min(wrapperEl.clientWidth - wrapperEl.scrollLeft - rdTipEl.clientWidth - 16, evnt.clientX - wrapperRect.x)}px`
        rdTipEl.setAttribute('drag-status', showLine ? (prevDragToChild ? 'sub' : 'normal') : 'disabled')
      }
    }

    const hideDropTip = () => {
      const rdTipEl = refDragTipElem.value
      const rdLineEl = refDragRowLineElem.value
      const cdLineEl = refDragColLineElem.value
      if (rdTipEl) {
        rdTipEl.style.display = ''
      }
      if (rdLineEl) {
        rdLineEl.style.display = ''
      }
      if (cdLineEl) {
        cdLineEl.style.display = ''
      }
    }

    const clearRowDragData = () => {
      let wrapperEl = refElem.value
      const dtClss = ['.vxe-body--row']
      if ($xeGantt) {
        const { refGanttContainerElem } = $xeGantt.getRefMaps()
        const ganttContainerElem = refGanttContainerElem.value
        if (ganttContainerElem) {
          wrapperEl = ganttContainerElem
        }
        dtClss.push('.vxe-gantt-view--body-row', '.vxe-gantt-view--chart-row')
      }
      hideDropTip()
      clearRowDropOrigin()
      clearRowAnimate(wrapperEl, dtClss)
      internalData.prevDragToChild = false
      reactData.dragRow = null
      reactData.dragCol = null
    }

    const clearColDragData = () => {
      const el = refElem.value
      hideDropTip()
      clearColDropOrigin()
      clearColAnimate(el, ['.vxe-table--column'])
      internalData.prevDragToChild = false
      reactData.dragRow = null
      reactData.dragCol = null
    }

    /**
     * 处理显示 tooltip
     * @param {Event} evnt 事件
     * @param {Row} row 行对象
     */
    const handleTooltip = (evnt: MouseEvent, tipOpts: VxeTablePropTypes.TooltipConfig | VxeTablePropTypes.HeaderTooltipConfig | VxeTablePropTypes.FooterTooltipConfig, type: 'header' | 'body' | 'footer', tdEl: HTMLTableCellElement, overflowElem: HTMLElement | null, tipElem: HTMLElement | null, params: any) => {
      const tipOverEl = overflowElem || tdEl
      if (!tipOverEl) {
        return nextTick()
      }
      params.cell = tdEl
      const { tooltipStore } = reactData
      const { column, row } = params
      const { showAll, contentMethod } = tipOpts
      const customContent = contentMethod ? contentMethod(params) : null
      const useCustom = contentMethod && !XEUtils.eqNull(customContent)
      const content = useCustom ? customContent : XEUtils.toString(column.type === 'html' ? tipOverEl.innerText : tipOverEl.textContent).trim()
      const isOver = tipOverEl.scrollWidth > tipOverEl.clientWidth
      if (content && (showAll || useCustom || isOver)) {
        const tipContent = formatText(content)
        Object.assign(tooltipStore, {
          row,
          column,
          visible: true,
          content: tipContent,
          type,
          currOpts: tipOpts
        })
        nextTick(() => {
          const $tooltip = refTooltip.value
          if ($tooltip && $tooltip.open) {
            $tooltip.open(isOver ? tipOverEl : tipElem, tipContent)
          }
        })
      }
      return nextTick()
    }

    const callSlot = <T>(slotFunc: ((params: T) => VxeComponentSlotType | VxeComponentSlotType[]) | string | null, params: T): VxeComponentSlotType[] => {
      if (slotFunc) {
        if ($xeGGWrapper) {
          return $xeGGWrapper.callSlot(slotFunc, params)
        }
        // if (XEUtils.isString(slotFunc)) {
        //   slotFunc = slots[slotFunc] || null
        // }
        if (XEUtils.isFunction(slotFunc)) {
          return getSlotVNs(slotFunc(params))
        }
      }
      return []
    }

    /**
     * 内部方法
     */
    tablePrivateMethods = {
      getSetupOptions () {
        return getConfig()
      },
      updateAfterDataIndex,
      callSlot,
      /**
       * 获取父容器元素
       */
      getParentElem () {
        const el = refElem.value
        if ($xeGGWrapper) {
          const gridEl = $xeGGWrapper.getRefMaps().refElem.value
          return gridEl ? gridEl.parentNode as HTMLElement : null
        }
        return el ? el.parentNode as HTMLElement : null
      },
      /**
       * 获取父容器的高度
       */
      getParentHeight () {
        const { height } = props
        const el = refElem.value
        if (el) {
          const parentElem = el.parentNode as HTMLElement
          let parentPaddingSize = 0
          let parentWrapperHeight = 0
          if (parentElem) {
            if ($xeGantt && hasClass(parentElem, 'vxe-gantt--table-wrapper')) {
              parentWrapperHeight = $xeGantt.getParentHeight()
            } else if ($xeGrid && hasClass(parentElem, 'vxe-grid--table-wrapper')) {
              parentWrapperHeight = $xeGrid.getParentHeight()
            } else {
              parentWrapperHeight = parentElem.clientHeight
              parentPaddingSize = height === '100%' || height === 'auto' ? getPaddingTopBottomSize(parentElem) : 0
            }
          }
          return Math.floor(parentWrapperHeight - parentPaddingSize)
        }
        return 0
      },
      /**
       * 获取需要排除的高度
       * 但渲染表格高度时，需要排除工具栏或分页等相关组件的高度
       * 如果存在表尾合计滚动条，则需要排除滚动条高度
       */
      getExcludeHeight () {
        return $xeGGWrapper ? $xeGGWrapper.getExcludeHeight() : 0
      },
      /**
       * 定义行数据中的列属性，如果不存在则定义
       * @param {Row} records 行数据
       */
      defineField (records) {
        const { treeConfig } = props
        const expandOpts = computeExpandOpts.value
        const treeOpts = computeTreeOpts.value
        const radioOpts = computeRadioOpts.value
        const rowOpts = computeRowOpts.value
        const checkboxOpts = computeCheckboxOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        const rowkey = getRowkey($xeTable)
        if (!XEUtils.isArray(records)) {
          records = [records]
        }
        return records.map(record => {
          internalData.tableFullColumn.forEach(column => {
            const { field, editRender } = column
            if (field && !XEUtils.has(record, field) && !record[field]) {
              let cellValue = null
              if (editRender) {
                const { defaultValue } = editRender
                if (XEUtils.isFunction(defaultValue)) {
                  cellValue = defaultValue({ column })
                } else if (!XEUtils.isUndefined(defaultValue)) {
                  cellValue = defaultValue
                }
              }
              XEUtils.set(record, field, cellValue)
            }
          })
          const otherFields: (string | undefined)[] = [radioOpts.labelField, checkboxOpts.checkField, checkboxOpts.labelField, expandOpts.labelField]
          otherFields.forEach((key) => {
            if (key && eqEmptyValue(XEUtils.get(record, key))) {
              XEUtils.set(record, key, null)
            }
          })
          if (treeConfig && treeOpts.lazy && XEUtils.isUndefined(record[childrenField])) {
            record[childrenField] = null
          }
          // 必须有行数据的唯一主键，可以自行设置；也可以默认生成一个随机数
          if (eqEmptyValue(XEUtils.get(record, rowkey))) {
            XEUtils.set(record, rowkey, createRowId(rowOpts, record, rowkey))
          }
          return record
        })
      },
      handleTableData (force?: boolean) {
        const { scrollYLoad } = reactData
        const { scrollYStore, fullDataRowIdData } = internalData
        const $xeGanttView = internalData.xeGanttView
        let fullList: any[] = internalData.afterFullData
        // 是否进行数据处理
        if (force) {
          // 更新数据，处理筛选和排序
          updateAfterFullData()
          // 如果为虚拟树，将树结构拍平
          fullList = handleVirtualTreeToList()
          // 更新数据后清除合并缓存，涉及分组、筛选、排序等
          internalData.mergeHeaderRowMaps = {}
          internalData.mergeHeaderColMaps = {}
          internalData.mergeBodyRowMaps = {}
          internalData.mergeBodyColMaps = {}
          internalData.mergeFooterRowMaps = {}
          internalData.mergeFooterColMaps = {}
        }
        const tableData = scrollYLoad ? fullList.slice(scrollYStore.startIndex, scrollYStore.endIndex) : fullList.slice(0)
        const visibleDataRowIdMaps: Record<string, any> = {}
        tableData.forEach((row, $index) => {
          const rowid = getRowid($xeTable, row)
          const rest = fullDataRowIdData[rowid]
          if (rest) {
            rest.$index = $index
          }
          visibleDataRowIdMaps[rowid] = row
        })
        reactData.tableData = tableData
        internalData.visibleDataRowIdData = visibleDataRowIdMaps
        if ($xeGanttView && $xeGanttView.updateViewData) {
          $xeGanttView.updateViewData(force)
        }
        return nextTick()
      },
      /**
       * 更新数据行的 Map
       */
      cacheRowMap (isReset) {
        const { treeConfig } = props
        const { isRowGroupStatus } = reactData
        const { currKeyField, fullAllDataRowIdData, tableFullData, tableFullTreeData, tableFullGroupData, treeExpandedMaps, rowExpandedMaps, selectCheckboxMaps } = internalData
        const fullAllDataRowIdMaps: Record<string, VxeTableDefines.RowCacheItem> = isReset ? {} : { ...fullAllDataRowIdData } // 存在已删除数据
        const fullDataRowIdMaps: Record<string, VxeTableDefines.RowCacheItem> = {}

        const idMaps: Record<string, boolean> = {}
        const { handleUpdateRowId } = createHandleUpdateRowId($xeTable)
        const handleRowCache = (row: any, index: number, items: any, currIndex: number, parentRow: any, rowid: string, level: number, seq: string | number) => {
          let rowRest = fullAllDataRowIdMaps[rowid]
          if (idMaps[rowid]) {
            errLog('vxe.error.repeatKey', [currKeyField, rowid])
          }
          if (!rowRest) {
            rowRest = { row, rowid, seq, index: -1, _index: -1, $index: -1, treeIndex: index, _tIndex: -1, items, parent: parentRow, level, height: 0, resizeHeight: 0, oTop: 0, expandHeight: 0 }
            fullDataRowIdMaps[rowid] = rowRest
            fullAllDataRowIdMaps[rowid] = rowRest
          }
          rowRest.treeLoaded = false
          rowRest.expandLoaded = false

          rowRest.row = row
          rowRest.items = items
          rowRest.parent = parentRow
          rowRest.level = level
          rowRest.index = currIndex
          rowRest.treeIndex = index

          // 更新缓存
          if (selectCheckboxMaps[rowid]) {
            selectCheckboxMaps[rowid] = row
          }
          if (rowExpandedMaps[rowid]) {
            rowExpandedMaps[rowid] = row
          }

          idMaps[rowid] = true
          fullDataRowIdMaps[rowid] = rowRest
          fullAllDataRowIdMaps[rowid] = rowRest
        }

        if (treeConfig) {
          const treeOpts = computeTreeOpts.value
          const { lazy } = treeOpts
          const childrenField = treeOpts.children || treeOpts.childrenField
          const hasChildField = treeOpts.hasChild || treeOpts.hasChildField
          XEUtils.eachTree(tableFullTreeData, (row, index, items, path, parentRow, nodes) => {
            const rowid = handleUpdateRowId(row)
            if (treeConfig && lazy) {
              if (row[hasChildField] && row[childrenField] === undefined) {
                row[childrenField] = null
              }
              if (treeExpandedMaps[rowid]) {
                if (!row[childrenField] || !row[childrenField].length) {
                  delete treeExpandedMaps[rowid]
                }
              }
            }
            handleRowCache(row, index, items, parentRow ? -1 : index, parentRow, rowid, nodes.length - 1, toTreePathSeq(path))
          }, { children: childrenField })
        } else if (isRowGroupStatus) {
          const aggregateOpts = computeAggregateOpts.value
          const { mapChildrenField } = aggregateOpts
          XEUtils.eachTree(tableFullGroupData, (row, index, items, path, parentRow, nodes) => {
            const rowid = handleUpdateRowId(row)
            handleRowCache(row, index, items, parentRow ? -1 : index, parentRow, rowid, nodes.length - 1, toTreePathSeq(path))
          }, { children: mapChildrenField })
        } else {
          tableFullData.forEach((row, index, items) => {
            handleRowCache(row, index, items, index, null, handleUpdateRowId(row), 0, index + 1)
          })
        }

        internalData.fullDataRowIdData = fullDataRowIdMaps
        internalData.fullAllDataRowIdData = fullAllDataRowIdMaps
        reactData.treeExpandedFlag++
      },
      cacheSourceMap (fullData) {
        const { treeConfig } = props
        const treeOpts = computeTreeOpts.value
        const sourceData = XEUtils.clone(fullData, true)
        const { handleUpdateRowId } = createHandleUpdateRowId($xeTable)
        const sourceRowIdData: Record<string, any> = {}
        const handleSourceRow = (row: any) => {
          const rowid = handleUpdateRowId(row)
          sourceRowIdData[rowid] = row
        }
        // 源数据缓存
        if (treeConfig) {
          const childrenField = treeOpts.children || treeOpts.childrenField
          XEUtils.eachTree(sourceData, handleSourceRow, { children: treeOpts.transform ? treeOpts.mapChildrenField : childrenField })
        } else {
          sourceData.forEach(handleSourceRow)
        }
        internalData.sourceDataRowIdData = sourceRowIdData
        internalData.tableSourceData = sourceData
      },
      /**
       * 指定列宽的列进行拆分
       */
      analyColumnWidth () {
        const { tableFullColumn } = internalData
        const columnOpts = computeColumnOpts.value
        const { width: defaultWidth, minWidth: defaultMinWidth } = columnOpts
        const resizeList: VxeTableDefines.ColumnInfo[] = []
        const pxList: VxeTableDefines.ColumnInfo[] = []
        const pxMinList: VxeTableDefines.ColumnInfo[] = []
        const autoMinList: VxeTableDefines.ColumnInfo[] = []
        const scaleList: VxeTableDefines.ColumnInfo[] = []
        const scaleMinList: VxeTableDefines.ColumnInfo[] = []
        const autoList: VxeTableDefines.ColumnInfo[] = []
        const remainList: VxeTableDefines.ColumnInfo[] = []
        tableFullColumn.forEach((column) => {
          if (defaultWidth && !column.width) {
            column.width = defaultWidth
          }
          if (defaultMinWidth && !column.minWidth) {
            column.minWidth = defaultMinWidth
          }
          if (column.visible) {
            if (column.resizeWidth) {
              resizeList.push(column)
            } else if (column.width === 'auto') {
              autoList.push(column)
            } else if (isPx(column.width)) {
              pxList.push(column)
            } else if (isScale(column.width)) {
              scaleList.push(column)
            } else if (isPx(column.minWidth)) {
              pxMinList.push(column)
            } else if (column.minWidth === 'auto') {
              autoMinList.push(column)
            } else if (isScale(column.minWidth)) {
              scaleMinList.push(column)
            } else {
              remainList.push(column)
            }
          }
        })
        Object.assign(reactData.columnStore, { resizeList, pxList, pxMinList, autoMinList, scaleList, scaleMinList, autoList, remainList })
      },
      handleColResizeMousedownEvent (evnt, fixedType, params) {
        const isLeftBtn = evnt.button === 0
        if (!isLeftBtn) {
          return
        }
        evnt.stopPropagation()
        evnt.preventDefault()
        const { column } = params
        const { columnStore, overflowX, scrollbarHeight } = reactData
        const { visibleColumn } = internalData
        const { leftList, rightList } = columnStore
        const resizableOpts = computeResizableOpts.value
        const osbHeight = overflowX ? scrollbarHeight : 0
        const tableEl = refElem.value
        const leftContainerElem = refLeftContainer.value
        const rightContainerElem = refRightContainer.value
        const resizeBarElem = refColResizeBar.value
        if (!resizeBarElem) {
          return
        }
        const isLeftFixed = fixedType === 'left'
        const isRightFixed = fixedType === 'right'
        const resizeTipElem = resizeBarElem.firstElementChild as HTMLDivElement
        const scrollbarXToTop = computeScrollbarXToTop.value
        const { clientX: dragClientX } = evnt
        const dragBtnElem = evnt.target as HTMLDivElement
        let cell = dragBtnElem.parentElement as HTMLTableCellElement | null
        let resizeColumn = column
        const isDragGroupCol = column.children && column.children.length
        if (isDragGroupCol) {
          resizeColumn = getLastChildColumn(column)
          if (isDragGroupCol) {
            const trEl = cell ? cell.parentElement as HTMLTableRowElement : null
            const theadEl = trEl ? trEl.parentElement as HTMLTableElement : null
            cell = theadEl ? theadEl.querySelector<HTMLTableCellElement>(`.vxe-header--column[colid="${resizeColumn.id}"]`) : null
          }
        }
        if (!cell) {
          return
        }
        const cellParams = XEUtils.assign(params, { cell, $table: $xeTable })
        let dragLeft = 0
        const tableRect = tableEl.getBoundingClientRect()
        const rightContainerRect = rightContainerElem ? rightContainerElem.getBoundingClientRect() : null
        const cellRect = cell.getBoundingClientRect()
        const dragBtnRect = dragBtnElem.getBoundingClientRect()

        const dragBtnWidth = dragBtnElem.clientWidth
        const dragBtnOffsetWidth = XEUtils.floor(dragBtnWidth / 2)
        const dragPosLeft = dragBtnRect.x - tableRect.x + dragBtnOffsetWidth

        const minInterval = getColReMinWidth(cellParams) - dragBtnOffsetWidth // 列之间的最小间距
        const dragMinLeft = isRightFixed ? 0 : (cellRect.x - tableRect.x + dragBtnWidth + minInterval)
        const dragMaxLeft = cellRect.x - tableRect.x + cell.clientWidth - minInterval

        let fixedLeftRemainWidth = 0
        let fixedRightRemainWidth = 0
        if (isLeftFixed || isRightFixed) {
          let isMach = false
          const fixedColumn = isLeftFixed ? leftList : rightList
          for (let i = 0; i < fixedColumn.length; i++) {
            const item = fixedColumn[i]
            if (isMach) {
              fixedLeftRemainWidth += item.renderWidth
            } else {
              isMach = item.id === resizeColumn.id
              if (!isMach) {
                fixedRightRemainWidth += item.renderWidth
              }
            }
          }
        }

        // 处理拖动事件
        const updateEvent = (evnt: MouseEvent) => {
          evnt.stopPropagation()
          evnt.preventDefault()
          const tableHeight = tableEl.clientHeight
          const offsetX = evnt.clientX - dragClientX
          let left = dragPosLeft + offsetX

          if (isLeftFixed) {
            if (rightContainerRect) {
              left = Math.min(left, rightContainerRect.x - tableRect.x - fixedLeftRemainWidth - minInterval)
            }
          } else if (isRightFixed) {
            if (leftContainerElem) {
              left = Math.max(left, leftContainerElem.clientWidth + fixedRightRemainWidth + minInterval)
            }
            left = Math.min(left, dragMaxLeft)
          }

          dragLeft = Math.max(left, dragMinLeft)

          const resizeBarLeft = Math.max(1, dragLeft)
          resizeBarElem.style.left = `${resizeBarLeft}px`
          resizeBarElem.style.top = `${scrollbarXToTop ? osbHeight : 0}px`
          resizeBarElem.style.height = `${scrollbarXToTop ? tableHeight - osbHeight : tableHeight}px`
          if (resizableOpts.showDragTip && resizeTipElem) {
            resizeTipElem.textContent = getI18n('vxe.table.resizeColTip', [Math.floor(resizeColumn.renderWidth + (isRightFixed ? dragPosLeft - dragLeft : dragLeft - dragPosLeft))])
            const tableWrapperWidth = tableEl.clientWidth
            const resizeBarWidth = resizeBarElem.clientWidth
            const resizeTipWidth = resizeTipElem.clientWidth
            const resizeTipHeight = resizeTipElem.clientHeight
            let resizeTipLeft = -resizeTipWidth
            if (resizeBarLeft < resizeTipWidth + resizeBarWidth) {
              resizeTipLeft = 0
            } else if (resizeBarLeft > tableWrapperWidth) {
              resizeTipLeft += tableWrapperWidth - resizeBarLeft
            }
            resizeTipElem.style.left = `${resizeTipLeft}px`
            resizeTipElem.style.top = `${Math.min(tableHeight - resizeTipHeight, Math.max(0, evnt.clientY - tableRect.y - resizeTipHeight / 2))}px`
          }
          reactData.isDragResize = true
        }

        reactData.isDragResize = true
        addClass(tableEl, 'col-drag--resize')
        resizeBarElem.style.display = 'block'
        document.onmousemove = updateEvent
        document.onmouseup = function (evnt) {
          document.onmousemove = null
          document.onmouseup = null
          resizeBarElem.style.display = 'none'
          internalData._lastResizeTime = Date.now()

          setTimeout(() => {
            reactData.isDragResize = false
          }, 50)

          const resizeWidth = resizeColumn.renderWidth + (isRightFixed ? dragPosLeft - dragLeft : dragLeft - dragPosLeft)
          const resizeParams = { ...params, resizeWidth, resizeColumn }
          if (resizableOpts.dragMode === 'fixed') {
            visibleColumn.forEach(item => {
              if (item.id !== resizeColumn.id) {
                if (!item.resizeWidth) {
                  item.resizeWidth = item.renderWidth
                }
              }
            })
          }

          if ($xeTable.handleColResizeCellAreaEvent) {
            $xeTable.handleColResizeCellAreaEvent(evnt, resizeParams)
          } else {
            resizeColumn.resizeWidth = resizeWidth
            handleUpdateColResize(evnt, resizeParams)
          }
          removeClass(tableEl, 'col-drag--resize')
        }
        updateEvent(evnt)
        if ($xeTable.closeMenu) {
          $xeTable.closeMenu()
        }
      },
      handleColResizeDblclickEvent (evnt, params) {
        const resizableOpts = computeResizableOpts.value
        const { isDblclickAutoWidth } = resizableOpts
        const el = refElem.value
        if (isDblclickAutoWidth && el) {
          evnt.stopPropagation()
          evnt.preventDefault()
          const { fullColumnIdData } = internalData
          const { column } = params
          let resizeColumn = column
          if (column.children && column.children.length) {
            XEUtils.eachTree(column.children, childColumn => {
              resizeColumn = childColumn
            })
          }
          const colid = resizeColumn.id
          const colRest = fullColumnIdData[colid]
          const dragBtnElem = evnt.target as HTMLDivElement
          const cell = dragBtnElem.parentNode as HTMLTableCellElement
          const cellParams = Object.assign(params, { cell, $table: $xeTable })
          const colMinWidth = getColReMinWidth(cellParams)

          el.setAttribute('data-calc-col', 'Y')
          let resizeWidth = calcColumnAutoWidth(resizeColumn, el)
          el.removeAttribute('data-calc-col')
          if (colRest) {
            resizeWidth = Math.max(resizeWidth, colRest.width)
          }
          resizeWidth = Math.max(colMinWidth, resizeWidth)
          const resizeParams = { ...params, resizeWidth, resizeColumn }
          reactData.isDragResize = false
          internalData._lastResizeTime = Date.now()

          if ($xeTable.handleColResizeDblclickCellAreaEvent) {
            $xeTable.handleColResizeDblclickCellAreaEvent(evnt, resizeParams)
          } else {
            resizeColumn.resizeWidth = resizeWidth
            handleUpdateColResize(evnt, resizeParams)
          }
        }
      },
      handleRowResizeMousedownEvent (evnt, params) {
        evnt.stopPropagation()
        evnt.preventDefault()
        const { row } = params
        const { showOverflow } = props
        const { overflowX, scrollbarWidth, overflowY, scrollbarHeight } = reactData
        const { elemStore, fullAllDataRowIdData } = internalData
        const osbWidth = overflowY ? scrollbarWidth : 0
        const osbHeight = overflowX ? scrollbarHeight : 0
        const scrollbarYToLeft = computeScrollbarYToLeft.value
        const resizableOpts = computeResizableOpts.value
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        let tableEl = refElem.value
        if ($xeGantt) {
          const { refGanttContainerElem } = $xeGantt.getRefMaps()
          const ganttContainerElem = refGanttContainerElem.value
          if (ganttContainerElem) {
            tableEl = ganttContainerElem
          }
        }
        const resizeBarElem = refRowResizeBar.value
        if (!resizeBarElem) {
          return
        }
        const { clientY: dragClientY } = evnt
        const resizeTipElem = resizeBarElem.firstElementChild as HTMLDivElement
        const dragBtnElem = evnt.currentTarget as HTMLDivElement
        const tdEl = dragBtnElem.parentNode as HTMLTableCellElement
        const trEl = tdEl.parentNode as HTMLTableCellElement
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        if (!bodyScrollElem) {
          return
        }
        const rowid = getRowid($xeTable, row)
        const rowRest = fullAllDataRowIdData[rowid]
        if (!rowRest) {
          return
        }
        const defaultRowHeight = computeDefaultRowHeight.value
        let currCellHeight = rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight
        if (!showOverflow) {
          currCellHeight = tdEl.clientHeight
        }
        const tableRect = tableEl.getBoundingClientRect()
        const trRect = trEl.getBoundingClientRect()
        const targetOffsetY = dragClientY - trRect.y - trEl.clientHeight
        let resizeHeight = currCellHeight
        const cellEl = tdEl.querySelector('.vxe-cell')
        let cellMinHeight = 0
        if (cellEl) {
          const cellStyle = getComputedStyle(cellEl)
          cellMinHeight = Math.max(1, Math.ceil(XEUtils.toNumber(cellStyle.paddingTop) + XEUtils.toNumber(cellStyle.paddingBottom)))
        }
        const minTop = trRect.y - tableRect.y + cellMinHeight
        // 处理拖动事件
        const updateEvent = (evnt: MouseEvent) => {
          evnt.stopPropagation()
          evnt.preventDefault()
          const rtWidth = tableEl.clientWidth - osbWidth
          const tableHeight = tableEl.clientHeight - osbHeight
          let dragTop = evnt.clientY - tableRect.y - targetOffsetY
          if (dragTop < minTop) {
            dragTop = minTop
          } else {
            resizeHeight = Math.max(cellMinHeight, currCellHeight + evnt.clientY - dragClientY)
          }
          resizeBarElem.style.left = `${scrollbarYToLeft ? osbWidth : 0}px`
          resizeBarElem.style.top = `${dragTop}px`
          resizeBarElem.style.width = `${rtWidth}px`
          if (resizableOpts.showDragTip && resizeTipElem) {
            resizeTipElem.textContent = getI18n('vxe.table.resizeRowTip', [resizeHeight])
            const resizeTipWidth = resizeTipElem.clientWidth
            const resizeTipHeight = resizeTipElem.clientHeight
            let resizeBarLeft = Math.max(2, evnt.clientX - tableRect.x)
            let resizeBarTop = 0
            if (resizeBarLeft + resizeTipWidth >= rtWidth - 2) {
              resizeBarLeft = rtWidth - resizeTipWidth - 2
            }
            if (dragTop + resizeTipHeight >= tableHeight) {
              resizeBarTop = tableHeight - (dragTop + resizeTipHeight)
            }
            resizeTipElem.style.left = `${resizeBarLeft}px`
            resizeTipElem.style.top = `${resizeBarTop}px`
          }
          reactData.isDragResize = true
        }

        reactData.isDragResize = true
        addClass(tableEl, 'row-drag--resize')
        resizeBarElem.style.display = 'block'
        document.onmousemove = updateEvent
        document.onmouseup = function (evnt) {
          document.onmousemove = null
          document.onmouseup = null
          resizeBarElem.style.display = 'none'
          internalData._lastResizeTime = Date.now()

          setTimeout(() => {
            reactData.isDragResize = false
          }, 50)

          if (resizeHeight !== currCellHeight) {
            const resizeParams = { ...params, resizeHeight, resizeRow: row }
            internalData.isResizeCellHeight = true
            if ($xeTable.handleRowResizeCellAreaEvent) {
              $xeTable.handleRowResizeCellAreaEvent(evnt, resizeParams)
            } else {
              rowRest.resizeHeight = resizeHeight
              handleUpdateRowResize(evnt, resizeParams)
              updateRowOffsetTop()
            }
          }
          removeClass(tableEl, 'row-drag--resize')
        }
        updateEvent(evnt)
      },
      handleRowResizeDblclickEvent (evnt, params) {
        const resizableOpts = computeResizableOpts.value
        const { isDblclickAutoHeight } = resizableOpts
        const el = refElem.value
        if (isDblclickAutoHeight && el) {
          evnt.stopPropagation()
          evnt.preventDefault()
          const { editStore } = reactData
          const { fullAllDataRowIdData } = internalData
          const { actived } = editStore
          const { row } = params
          const rowid = getRowid($xeTable, row)
          const rowRest = fullAllDataRowIdData[rowid]
          if (!rowRest) {
            return
          }
          const handleRsHeight = () => {
            el.setAttribute('data-calc-row', 'Y')
            const resizeHeight = calcCellAutoHeight(rowRest, el)
            el.removeAttribute('data-calc-row')
            const resizeParams = { ...params, resizeHeight, resizeRow: row }
            reactData.isDragResize = false
            internalData._lastResizeTime = Date.now()
            if ($xeTable.handleRowResizeDblclickCellAreaEvent) {
              $xeTable.handleRowResizeDblclickCellAreaEvent(evnt, resizeParams)
            } else {
              rowRest.resizeHeight = resizeHeight
              handleUpdateRowResize(evnt, resizeParams)
            }
          }
          if (actived.row || actived.column) {
            $xeTable.clearEdit().then(handleRsHeight)
          } else {
            handleRsHeight()
          }
        }
      },
      saveCustomStore (type) {
        const { customConfig } = props
        const tableId = computeTableId.value
        const customOpts = computeCustomOpts.value
        const { updateStore, storage, storeOptions } = customOpts
        const isAllCustom = storage === true
        const storageOpts: VxeTableDefines.VxeTableCustomStorageObj = Object.assign({}, isAllCustom ? {} : storage || {}, storeOptions)
        const isCustomResizable = hangleStorageDefaultValue(storageOpts.resizable, isAllCustom)
        const isCustomVisible = hangleStorageDefaultValue(storageOpts.visible, isAllCustom)
        const isCustomFixed = hangleStorageDefaultValue(storageOpts.fixed, isAllCustom)
        const isCustomSort = hangleStorageDefaultValue(storageOpts.sort, isAllCustom)
        const isCustomAggGroup = hangleStorageDefaultValue(storageOpts.aggGroup, isAllCustom)
        const isCustomAggFunc = hangleStorageDefaultValue(storageOpts.aggFunc, isAllCustom)
        if (type !== 'reset') {
          // fix：修复拖动列宽，重置按钮无法点击的问题
          reactData.isCustomStatus = true
        }
        if (storage && (customConfig ? isEnableConf(customOpts) : customOpts.enabled) && (isCustomResizable || isCustomVisible || isCustomFixed || isCustomSort || isCustomAggGroup || isCustomAggFunc)) {
          if (!tableId) {
            errLog('vxe.error.reqProp', ['id'])
            return nextTick()
          }
          const storeData: VxeTableDefines.CustomStoreData = type === 'reset'
            ? {
                resizableData: {},
                sortData: [],
                visibleData: {},
                fixedData: {},
                aggGroupData: {},
                aggFuncData: {}
              }
            : $xeTable.getCustomStoreData()
          if (updateStore) {
            return updateStore({
              $table: $xeTable,
              id: tableId,
              type,
              storeData
            })
          } else {
            setCustomStorageMap(tableId, type === 'reset' ? null : storeData)
          }
        }
        return nextTick()
      },
      handleCustom () {
        const { mouseConfig } = props
        if (mouseConfig) {
          if ($xeTable.clearSelected) {
            $xeTable.clearSelected()
          }
          if ($xeTable.clearCellAreas) {
            $xeTable.clearCellAreas()
            $xeTable.clearCopyCellArea()
          }
        }
        $xeTable.analyColumnWidth()
        return $xeTable.refreshColumn(true)
      },
      handleUpdateDataQueue () {
        reactData.upDataFlag++
      },
      handleRefreshColumnQueue () {
        reactData.reColumnFlag++
      },
      handleFilterOptions (column) {
        if (column) {
          const { filterStore } = reactData
          const { filterRender, filters } = column
          const filterOptions = filters || []
          const compConf = isEnableConf(filterRender) ? renderer.get(filterRender.name) : null
          const frMethod = column.filterRecoverMethod || (compConf ? (compConf.tableFilterRecoverMethod || compConf.filterRecoverMethod) : null)
          filterStore.column = column
          // 复原状态
          filterOptions.forEach((option: any) => {
            const { _checked, checked } = option
            option._checked = checked
            if (!checked && _checked !== checked) {
              if (frMethod) {
                frMethod({ option, column, $table: $xeTable })
              }
            }
          })
          $xeTable.checkFilterOptions()
        }
      },
      preventEvent (evnt, type, args, next, end) {
        let evntList = interceptor.get(type)

        // 兼容老版本
        if (!evntList.length && type === 'event.clearEdit') {
          evntList = interceptor.get('event.clearActived')
          if (evntList.length) {
            warnLog('vxe.error.delEvent', ['event.clearActived', 'event.clearEdit'])
          }
        }
        // 兼容老版本

        let rest = null
        let isStop = false
        for (let i = 0; i < evntList.length; i++) {
          const func = evntList[i]
          const fnRest = func(Object.assign({ $table: $xeTable, $grid: $xeGrid, gantt: $xeGantt, $event: evnt }, args))
          if (fnRest === false) {
            isStop = true
            break
          } else if (fnRest && fnRest.status === false) {
            rest = fnRest.result
            isStop = true
            break
          }
        }
        if (!isStop) {
          if (next) {
            rest = next()
          }
        }
        if (end) {
          end()
        }
        return rest
      },
      updateCheckboxStatus () {
        const { treeConfig } = props
        const { isRowGroupStatus } = reactData
        const { afterTreeFullData, afterGroupFullData, selectCheckboxMaps, treeIndeterminateRowMaps } = internalData
        const aggregateOpts = computeAggregateOpts.value
        const treeOpts = computeTreeOpts.value
        const childrenField = treeOpts.children || treeOpts.childrenField
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField, indeterminateField, checkStrictly, checkMethod } = checkboxOpts
        if (checkStrictly) {
          return
        }
        if (isRowGroupStatus || treeConfig) {
          const { handleGetRowId } = createHandleGetRowId($xeTable)
          const childRowMaps: Record<string, number> = {}
          const childRowList: any[][] = []

          if (isRowGroupStatus) {
            // 行分组
            const mapChildrenField = aggregateOpts.mapChildrenField
            if (mapChildrenField) {
              XEUtils.eachTree(afterGroupFullData, (row) => {
                const rowid = handleGetRowId(row)
                const childList = row[mapChildrenField]
                if (childList && childList.length && !childRowMaps[rowid]) {
                  childRowMaps[rowid] = 1
                  childRowList.unshift([row, rowid, childList])
                }
              }, { children: mapChildrenField })
            }
          } else if (treeConfig) {
            // 树结构
            const { transform, mapChildrenField } = treeOpts
            XEUtils.eachTree(afterTreeFullData, (row) => {
              const rowid = handleGetRowId(row)
              const childList = row[transform ? mapChildrenField : childrenField]
              if (childList && childList.length && !childRowMaps[rowid]) {
                childRowMaps[rowid] = 1
                childRowList.unshift([row, rowid, childList])
              } else {
                if (indeterminateField) {
                  XEUtils.set(row, indeterminateField, false)
                }
              }
            }, { children: transform ? mapChildrenField : childrenField })
          }

          childRowList.forEach(vals => {
            const row = vals[0]
            const rowid: string = vals[1]
            const childList: any[] = vals[2]
            let sLen = 0 // 已选
            let hLen = 0 // 半选
            let vLen = 0 // 有效子行
            const cLen = childList.length // 子行
            childList.forEach(
              checkMethod
                ? (item) => {
                    const childRowid = handleGetRowId(item)
                    const isSelect = checkField ? XEUtils.get(item, checkField) : selectCheckboxMaps[childRowid]
                    if (checkMethod({ $table: $xeTable, row: item })) {
                      if (isSelect) {
                        sLen++
                      } else if (treeIndeterminateRowMaps[childRowid]) {
                        hLen++
                      }
                      vLen++
                    } else {
                      if (isSelect) {
                        sLen++
                      } else if (treeIndeterminateRowMaps[childRowid]) {
                        hLen++
                      }
                    }
                  }
                : item => {
                  const childRowid = handleGetRowId(item)
                  const isSelect = checkField ? XEUtils.get(item, checkField) : selectCheckboxMaps[childRowid]
                  if (isSelect) {
                    sLen++
                  } else if (treeIndeterminateRowMaps[childRowid]) {
                    hLen++
                  }
                  vLen++
                }
            )

            let isSelected = false
            if (cLen > 0) {
              if (vLen > 0) {
                isSelected = (sLen > 0 || hLen > 0) && sLen >= vLen
              } else {
                // 如果存在子项禁用
                if ((sLen > 0 && sLen >= vLen)) {
                  isSelected = true
                } else if (selectCheckboxMaps[rowid]) {
                  isSelected = true
                } else {
                  isSelected = false
                }
              }
            } else {
              // 如果无子项
              isSelected = selectCheckboxMaps[rowid]
            }
            const halfSelect = !isSelected && (sLen > 0 || hLen > 0)

            if (checkField) {
              XEUtils.set(row, checkField, isSelected)
            }
            if (indeterminateField) {
              XEUtils.set(row, indeterminateField, halfSelect)
            }
            if (isSelected) {
              if (!checkField) {
                selectCheckboxMaps[rowid] = row
              }
              if (treeIndeterminateRowMaps[rowid]) {
                delete treeIndeterminateRowMaps[rowid]
              }
            } else {
              if (!checkField) {
                if (selectCheckboxMaps[rowid]) {
                  delete selectCheckboxMaps[rowid]
                }
              }
              if (halfSelect) {
                treeIndeterminateRowMaps[rowid] = row
              } else {
                if (treeIndeterminateRowMaps[rowid]) {
                  delete treeIndeterminateRowMaps[rowid]
                }
              }
            }
          })
        }
        reactData.updateCheckboxFlag++
      },
      updateAllCheckboxStatus () {
        const { treeConfig } = props
        const { isRowGroupStatus } = reactData
        const { afterFullData, afterTreeFullData, afterGroupFullData, checkboxReserveRowMap, selectCheckboxMaps, treeIndeterminateRowMaps } = internalData
        const aggregateOpts = computeAggregateOpts.value
        const { mapChildrenField } = aggregateOpts
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField, checkMethod, showReserveStatus } = checkboxOpts
        const { handleGetRowId } = createHandleGetRowId($xeTable)

        let sLen = 0 // 已选
        let dsLen = 0 // 禁用的已选
        let hLen = 0 // 半选
        let dhLen = 0 // 禁用的半选
        let vLen = 0 // 有效行

        const rootList = (treeConfig ? afterTreeFullData : (isRowGroupStatus ? afterGroupFullData : afterFullData))
        rootList.forEach(checkMethod
          ? row => {
            const childRowid = handleGetRowId(row)
            const selected = checkField ? XEUtils.get(row, checkField) : selectCheckboxMaps[childRowid]
            if (isRowGroupStatus && $xeTable.isAggregateRecord(row)) {
              const childList: any[] = row[mapChildrenField || '']
              if (selected) {
                vLen++
                sLen++
              } else if (treeIndeterminateRowMaps[childRowid]) {
                vLen++
                hLen++
              } else if (childList && childList.length && childList.some(item => checkMethod({ $table: $xeTable, row: item }))) {
                vLen++
              }
            } else if (checkMethod({ $table: $xeTable, row })) {
              if (selected) {
                sLen++
              } else if (treeIndeterminateRowMaps[childRowid]) {
                hLen++
              }
              vLen++
            } else {
              if (selected) {
                dsLen++
              } else if (treeIndeterminateRowMaps[childRowid]) {
                dhLen++
              }
            }
          }
          : row => {
            const childRowid = handleGetRowId(row)
            const selected = checkField ? XEUtils.get(row, checkField) : selectCheckboxMaps[childRowid]
            if (selected) {
              sLen++
            } else if (treeIndeterminateRowMaps[childRowid]) {
              hLen++
            }
            vLen++
          })

        const isSelected = rootList.length > 0 ? (vLen > 0 ? (sLen >= vLen) : (sLen >= rootList.length)) : false
        let halfSelect = !isSelected && (sLen > 0 || hLen > 0 || dsLen > 0 || dhLen > 0)

        // 如果复选框启用保留记录，当保留数据存在时显示半选
        if (!isSelected && !halfSelect && showReserveStatus) {
          halfSelect = !XEUtils.isEmpty(checkboxReserveRowMap)
        }

        reactData.isAllSelected = isSelected
        reactData.isIndeterminate = halfSelect
      },
      checkSelectionStatus () {
        $xeTable.updateCheckboxStatus()
        $xeTable.updateAllCheckboxStatus()
      },
      /**
       * 切换选中
       * 多选，行选中事件
       */
      handleBatchSelectRows (rows, checked, isForce) {
        const { treeConfig } = props
        const { isRowGroupStatus } = reactData
        const { selectCheckboxMaps } = internalData
        const aggregateOpts = computeAggregateOpts.value
        const treeOpts = computeTreeOpts.value
        const { transform, mapChildrenField } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField, checkStrictly, checkMethod } = checkboxOpts
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        // indeterminateField 仅支持读取
        const indeterminateField = checkboxOpts.indeterminateField || checkboxOpts.halfField
        if (checkField) {
          // 树结构
          if ((treeConfig || isRowGroupStatus) && !checkStrictly) {
            // 更新子节点状态
            XEUtils.eachTree(rows, (row) => {
              if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
                XEUtils.set(row, checkField, checked)
                if (indeterminateField) {
                  XEUtils.set(row, indeterminateField, false)
                }
                handleCheckboxReserveRow(row, checked)
              }
            }, { children: transform ? mapChildrenField : childrenField })
            reactData.updateCheckboxFlag++
            return
          }
          // 列表
          rows.forEach(row => {
            if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
              XEUtils.set(row, checkField, checked)
              handleCheckboxReserveRow(row, checked)
            }
          })
          reactData.updateCheckboxFlag++
          return
        }

        // 树结构
        if (!checkStrictly) {
          if (isRowGroupStatus) {
            // 更新行分组节点状态
            XEUtils.eachTree(rows, (row) => {
              const rowid = handleGetRowId(row)
              if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
                if (checked) {
                  selectCheckboxMaps[rowid] = row
                } else {
                  if (selectCheckboxMaps[rowid]) {
                    delete selectCheckboxMaps[rowid]
                  }
                }
                handleCheckboxReserveRow(row, checked)
              }
            }, { children: aggregateOpts.mapChildrenField })
            reactData.updateCheckboxFlag++
            return
          } else if (treeConfig) {
            // 更新子节点状态
            XEUtils.eachTree(rows, (row) => {
              const rowid = handleGetRowId(row)
              if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
                if (checked) {
                  selectCheckboxMaps[rowid] = row
                } else {
                  if (selectCheckboxMaps[rowid]) {
                    delete selectCheckboxMaps[rowid]
                  }
                }
                handleCheckboxReserveRow(row, checked)
              }
            }, { children: transform ? mapChildrenField : childrenField })
            reactData.updateCheckboxFlag++
            return
          }
        }

        // 列表
        rows.forEach(row => {
          const rowid = handleGetRowId(row)
          if (isForce || (!checkMethod || checkMethod({ $table: $xeTable, row }))) {
            if (checked) {
              if (!selectCheckboxMaps[rowid]) {
                selectCheckboxMaps[rowid] = row
              }
            } else {
              if (selectCheckboxMaps[rowid]) {
                delete selectCheckboxMaps[rowid]
              }
            }
            handleCheckboxReserveRow(row, checked)
            reactData.updateCheckboxFlag++
          }
        })
      },
      /**
       * 即将移除
       * @deprecated
       */
      handleSelectRow ({ row }, checked, isForce) {
        $xeTable.handleBatchSelectRows([row], checked, isForce)
      },
      handleUpdateHeaderMerge () {
        const { mergeHeaderList } = internalData
        const { mergeMaps, mergeRowMaps, mergeColMaps } = buildMergeData(mergeHeaderList)
        internalData.mergeHeaderCellMaps = mergeMaps
        internalData.mergeHeaderRowMaps = mergeRowMaps
        internalData.mergeHeaderColMaps = mergeColMaps
        reactData.mergeHeadFlag++
      },
      /**
       * 处理合并
       */
      handleUpdateBodyMerge () {
        const { mergeBodyList } = internalData
        const { mergeMaps, mergeRowMaps, mergeColMaps } = buildMergeData(mergeBodyList)
        internalData.mergeBodyCellMaps = mergeMaps
        internalData.mergeBodyRowMaps = mergeRowMaps
        internalData.mergeBodyColMaps = mergeColMaps
        reactData.mergeBodyFlag++
      },
      handleUpdateFooterMerge () {
        const { mergeFooterList } = internalData
        const { mergeMaps, mergeRowMaps, mergeColMaps } = buildMergeData(mergeFooterList)
        internalData.mergeFooterCellMaps = mergeMaps
        internalData.mergeFooterRowMaps = mergeRowMaps
        internalData.mergeFooterColMaps = mergeColMaps
        reactData.mergeFootFlag++
      },
      handleAggregateSummaryData () {
        return $xeTable.refreshAggregateCalcValues()
      },
      triggerHeaderTitleEvent (evnt, iconParams, params) {
        const tipContent = iconParams.content || (iconParams as any).message
        if (tipContent) {
          const { tooltipStore } = reactData
          const { column } = params
          const content = getFuncText(tipContent)
          handleTargetEnterEvent(true)
          tooltipStore.row = null
          tooltipStore.column = column
          tooltipStore.visible = true
          tooltipStore.currOpts = iconParams
          nextTick(() => {
            const $tooltip = refTooltip.value
            if ($tooltip && $tooltip.open) {
              $tooltip.open(evnt.currentTarget, content)
            }
          })
        }
      },
      /**
       * 触发表头 tooltip 事件
       */
      triggerHeaderTooltipEvent (evnt, params) {
        const { tooltipStore } = reactData
        const headerTooltipOpts = computeHeaderTooltipOpts.value
        const { column } = params
        handleTargetEnterEvent(true)
        const titleElem = evnt.currentTarget as HTMLDivElement
        if (!titleElem) {
          return
        }
        const cWrapperEl = titleElem.parentElement as HTMLDivElement
        if (!cWrapperEl) {
          return
        }
        const cellEl = cWrapperEl.parentElement as HTMLDivElement
        if (!cellEl) {
          return
        }
        const thEl = cellEl.parentElement as HTMLTableCellElement
        if (!thEl) {
          return
        }
        if (tooltipStore.column !== column || !tooltipStore.visible) {
          const ctEl = thEl.querySelector<HTMLElement>('.vxe-cell--title')
          handleTooltip(evnt, headerTooltipOpts, 'header', thEl, (hasClass(thEl, 'col--ellipsis') ? ctEl : cWrapperEl) || cWrapperEl, ctEl || cellEl, params)
        }
      },
      /**
       * 触发单元格 tooltip 事件
       */
      triggerBodyTooltipEvent (evnt, params) {
        const { editConfig } = props
        const { editStore } = reactData
        const { tooltipStore } = reactData
        const tooltipOpts = computeTooltipOpts.value
        const editOpts = computeEditOpts.value
        const { actived } = editStore
        const { row, column } = params
        const tdEl = evnt.currentTarget as HTMLTableCellElement
        handleTargetEnterEvent(tooltipStore.column !== column || tooltipStore.row !== row)
        // 单元格处于编辑状态时不触发提示框
        if (column.editRender && isEnableConf(editConfig)) {
          // 如果是行编辑模式
          if (editOpts.mode === 'row' && actived.row === row) {
            return
          }
          // 如果是单元格编辑模式
          if (actived.row === row && actived.column === column) {
            return
          }
        }
        if (tooltipStore.column !== column || tooltipStore.row !== row || !tooltipStore.visible) {
          const ctEl = tdEl.querySelector<HTMLElement>('.vxe-cell--wrapper')
          let ovEl = null
          let tipEl = tdEl.querySelector<HTMLElement>(column.type === 'html' ? '.vxe-cell--html' : '.vxe-cell--label')
          if (column.treeNode) {
            ovEl = tdEl.querySelector<HTMLElement>('.vxe-tree-cell')
          }
          if (!tipEl) {
            tipEl = ctEl
          }
          handleTooltip(evnt, tooltipOpts, 'body', tdEl, ovEl || ctEl, tipEl, params)
        }
      },
      /**
       * 触发表尾 tooltip 事件
       */
      triggerFooterTooltipEvent (evnt, params) {
        const { column } = params
        const { tooltipStore } = reactData
        const footerTooltipOpts = computeFooterTooltipOpts.value
        const tdEl = evnt.currentTarget as HTMLTableCellElement
        handleTargetEnterEvent(tooltipStore.column !== column || !!tooltipStore.row)
        if (tooltipStore.column !== column || !tooltipStore.visible) {
          const ctEl = tdEl.querySelector<HTMLElement>('.vxe-cell--wrapper')
          let ovEl = null
          let tipEl = tdEl.querySelector<HTMLElement>(column.type === 'html' ? '.vxe-cell--html' : '.vxe-cell--label')
          if (column.type === 'html') {
            ovEl = tdEl.querySelector<HTMLElement>('.vxe-cell--html')
          }
          if (!tipEl) {
            tipEl = ctEl
          }
          handleTooltip(evnt, footerTooltipOpts, 'footer', tdEl, ovEl || ctEl, tipEl, params)
        }
      },
      handleTargetLeaveEvent () {
        const tooltipOpts = computeTooltipOpts.value
        let $tooltip = refTooltip.value
        if ($tooltip && $tooltip.setActived) {
          $tooltip.setActived(false)
        }
        if (tooltipOpts.enterable) {
          internalData.tooltipTimeout = setTimeout(() => {
            $tooltip = refTooltip.value
            if ($tooltip && $tooltip.isActived && !$tooltip.isActived()) {
              $xeTable.closeTooltip()
            }
          }, tooltipOpts.leaveDelay)
        } else {
          $xeTable.closeTooltip()
        }
      },
      triggerHeaderCellClickEvent (evnt, params) {
        const { _lastResizeTime } = internalData
        const sortOpts = computeSortOpts.value
        const columnOpts = computeColumnOpts.value
        const currentColumnOpts = computeCurrentColumnOpts.value
        const { column } = params
        const cell = evnt.currentTarget
        const triggerResizable = _lastResizeTime && _lastResizeTime > Date.now() - 300
        const triggerSort = getEventTargetNode(evnt, cell, 'vxe-cell--sort').flag
        const triggerFilter = getEventTargetNode(evnt, cell, 'vxe-cell--filter').flag
        if (sortOpts.trigger === 'cell' && !(triggerResizable || triggerSort || triggerFilter)) {
          $xeTable.triggerSortEvent(evnt, column, getNextSortOrder(column))
        }
        if ($xeGantt) {
          const ganttReactData = $xeGantt.reactData
          ganttReactData.activeBarRowid = null
        }
        dispatchEvent('header-cell-click', Object.assign({ triggerResizable, triggerSort, triggerFilter, cell }, params), evnt)
        if ((columnOpts.isCurrent || props.highlightCurrentColumn) && (!currentColumnOpts.trigger || ['header', 'default'].includes(currentColumnOpts.trigger))) {
          $xeTable.triggerCurrentColumnEvent(evnt, params)
        }
      },
      triggerHeaderCellDblclickEvent (evnt, params) {
        dispatchEvent('header-cell-dblclick', Object.assign({ cell: evnt.currentTarget }, params), evnt)
      },
      /**
       * 列点击事件
       * 如果是单击模式，则激活为编辑状态
       * 如果是双击模式，则单击后选中状态
       */
      triggerCellClickEvent (evnt, params) {
        const { treeConfig, highlightCurrentRow, highlightCurrentColumn, editConfig, aggregateConfig, rowGroupConfig } = props
        const { editStore, isDragResize, expandColumn, checkboxColumn, radioColumn } = reactData
        if (isDragResize) {
          return
        }
        const expandOpts = computeExpandOpts.value
        const editOpts = computeEditOpts.value
        const treeOpts = computeTreeOpts.value
        const radioOpts = computeRadioOpts.value
        const checkboxOpts = computeCheckboxOpts.value
        const keyboardOpts = computeKeyboardOpts.value
        const aggregateOpts = computeAggregateOpts.value
        const rowOpts = computeRowOpts.value
        const columnOpts = computeColumnOpts.value
        const currentColumnOpts = computeCurrentColumnOpts.value
        const { actived, focused } = editStore
        const { row, column } = params
        const { type, treeNode, rowGroupNode } = column
        const isRadioType = type === 'radio'
        const isCheckboxType = type === 'checkbox'
        const isExpandType = type === 'expand'
        const cell = evnt.currentTarget as HTMLDivElement
        const triggerRadio = isRadioType && getEventTargetNode(evnt, cell, 'vxe-cell--radio').flag
        const triggerCheckbox = isCheckboxType && getEventTargetNode(evnt, cell, 'vxe-cell--checkbox').flag
        const triggerTreeNode = treeNode && getEventTargetNode(evnt, cell, 'vxe-cell--tree-btn').flag
        const triggerExpandNode = isExpandType && getEventTargetNode(evnt, cell, 'vxe-table--expanded').flag
        const triggerRowGroupNode = isExpandType && getEventTargetNode(evnt, cell, 'vxe-row-group--node-btn').flag
        params = Object.assign({ cell, triggerRadio, triggerCheckbox, triggerTreeNode, triggerExpandNode }, params)
        if (!triggerCheckbox && !triggerRadio) {
          // 如果是展开行
          if (!triggerExpandNode && ((expandColumn && expandOpts.trigger === 'row') || (isExpandType && expandOpts.trigger === 'cell'))) {
            $xeTable.triggerRowExpandEvent(evnt, params)
          }
          // 如果是树形表格
          if (treeConfig && (treeOpts.trigger === 'row' || (treeNode && treeOpts.trigger === 'cell'))) {
            $xeTable.triggerTreeExpandEvent(evnt, params)
          }
          // 如果是行分组
          if ((aggregateConfig || rowGroupConfig) && (aggregateOpts.trigger === 'row' || (rowGroupNode && aggregateOpts.trigger === 'cell'))) {
            $xeTable.triggerRowGroupExpandEvent(evnt, params)
          }
        }
        // 如果点击了树节点
        if (!triggerTreeNode) {
          if (!triggerExpandNode && !triggerRowGroupNode) {
            // 如果是当前行
            if (rowOpts.isCurrent || highlightCurrentRow) {
              if (!triggerCheckbox && !triggerRadio) {
                $xeTable.triggerCurrentRowEvent(evnt, params)
              }
            }
            // 如果是当前列
            if ((columnOpts.isCurrent || highlightCurrentColumn) && (!currentColumnOpts.trigger || ['cell', 'default'].includes(currentColumnOpts.trigger))) {
              if (!triggerCheckbox && !triggerRadio) {
                $xeTable.triggerCurrentColumnEvent(evnt, params)
              }
            }
            // 如果是单选框
            if (!triggerRadio && ((radioColumn && radioOpts.trigger === 'row') || (isRadioType && radioOpts.trigger === 'cell'))) {
              $xeTable.triggerRadioRowEvent(evnt, params)
            }
            // 如果是复选框
            if (!triggerCheckbox && ((checkboxColumn && checkboxOpts.trigger === 'row') || (isCheckboxType && checkboxOpts.trigger === 'cell'))) {
              $xeTable.handleToggleCheckRowEvent(evnt, params)
            }
          }
          // 如果设置了单元格选中功能，则不会使用点击事件去处理（只能支持双击模式）
          if (isEnableConf(editConfig)) {
            // 记录点击输入框聚焦状态
            if (keyboardOpts.arrowCursorLock && evnt && editOpts.mode === 'cell' && evnt.target && /^input|textarea$/i.test((evnt.target as HTMLElement).tagName)) {
              focused.column = column
              focused.row = row
            }
            if (editOpts.trigger === 'manual') {
              if (actived.args && actived.row === row && column !== actived.column) {
                handleChangeCell(evnt, params)
              }
            } else if (!actived.args || row !== actived.row || column !== actived.column) {
              if (editOpts.trigger === 'click') {
                handleChangeCell(evnt, params)
              } else if (editOpts.trigger === 'dblclick') {
                if (editOpts.mode === 'row' && actived.row === row) {
                  handleChangeCell(evnt, params)
                }
              }
            }
          }
        }
        // 如果是双击编辑模式
        if (isEnableConf(editConfig) && editOpts.trigger === 'dblclick') {
          if (actived.row && actived.column) {
            if (editOpts.mode === 'row') {
              if (!$xeTable.eqRow(actived.row, row)) {
                $xeTable.handleClearEdit(evnt)
              }
            } else if (editOpts.mode === 'cell') {
              if (!$xeTable.eqRow(actived.row, row) || actived.column.id !== column.id) {
                $xeTable.handleClearEdit(evnt)
              }
            }
          }
        }
        if ($xeGantt) {
          const ganttReactData = $xeGantt.reactData
          ganttReactData.activeBarRowid = null
        }
        dispatchEvent('cell-click', params, evnt)
      },
      /**
       * 列双击点击事件
       * 如果是双击模式，则激活为编辑状态
       */
      triggerCellDblclickEvent (evnt, params) {
        const { editConfig } = props
        const { editStore, isDragResize } = reactData
        if (isDragResize) {
          return
        }
        const editOpts = computeEditOpts.value
        const { actived } = editStore
        const cell = evnt.currentTarget as HTMLDivElement
        params = Object.assign({ cell }, params)
        if (isEnableConf(editConfig) && editOpts.trigger === 'dblclick') {
          if (!actived.args || evnt.currentTarget !== actived.args.cell) {
            if (editOpts.mode === 'row') {
              checkValidate('blur')
                .catch((e) => e)
                .then(() => {
                  $xeTable.handleEdit(params, evnt)
                    .then(() => checkValidate('change'))
                    .catch((e) => e)
                })
            } else if (editOpts.mode === 'cell') {
              $xeTable.handleEdit(params, evnt)
                .then(() => checkValidate('change'))
                .catch((e) => e)
            }
          }
        }
        dispatchEvent('cell-dblclick', params, evnt)
      },
      triggerFooterCellClickEvent (evnt, params) {
        const cell = evnt.currentTarget as HTMLDivElement
        params = Object.assign({ cell }, params)
        if ($xeGantt) {
          const ganttReactData = $xeGantt.reactData
          ganttReactData.activeBarRowid = null
        }
        $xeTable.dispatchEvent('footer-cell-click', params, evnt)
      },
      triggerFooterCellDblclickEvent (evnt, params) {
        const cell = evnt.currentTarget as HTMLDivElement
        params = Object.assign({ cell }, params)
        $xeTable.dispatchEvent('footer-cell-dblclick', params, evnt)
      },
      handleToggleCheckRowEvent (evnt, params) {
        const { selectCheckboxMaps } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkField, trigger } = checkboxOpts
        const { row } = params
        if (trigger === 'manual') {
          return
        }
        let checked = false
        if (checkField) {
          checked = !XEUtils.get(row, checkField)
        } else {
          checked = !selectCheckboxMaps[getRowid($xeTable, row)]
        }
        if (evnt) {
          $xeTable.triggerCheckRowEvent(evnt, params, checked)
        } else {
          $xeTable.handleBatchSelectRows([row], checked)
          $xeTable.checkSelectionStatus()
        }
      },
      triggerCheckRowEvent (evnt: MouseEvent, params, checked) {
        const { treeConfig } = props
        const { row } = params
        const { isRowGroupStatus } = reactData
        const { afterFullData } = internalData
        const checkboxOpts = computeCheckboxOpts.value
        const { checkMethod, trigger } = checkboxOpts
        if (trigger === 'manual') {
          return
        }
        evnt.stopPropagation()
        if (checkboxOpts.isShiftKey && evnt.shiftKey && !(treeConfig || isRowGroupStatus)) {
          const checkboxRecords = $xeTable.getCheckboxRecords()
          if (checkboxRecords.length) {
            const firstRow = checkboxRecords[0]
            const _rowIndex = $xeTable.getVTRowIndex(row)
            const _firstRowIndex = $xeTable.getVTRowIndex(firstRow)
            if (_rowIndex !== _firstRowIndex) {
              $xeTable.setAllCheckboxRow(false)
              const rangeRows = _rowIndex < _firstRowIndex ? afterFullData.slice(_rowIndex, _firstRowIndex + 1) : afterFullData.slice(_firstRowIndex, _rowIndex + 1)
              nextTick(() => {
                handleCheckedCheckboxRow(rangeRows, true, false)
              })
              dispatchEvent('checkbox-range-select', Object.assign({ rangeRecords: rangeRows }, params), evnt)
              return
            }
          }
        }
        if (isRowGroupStatus || !checkMethod || checkMethod({ $table: $xeTable, row })) {
          $xeTable.handleBatchSelectRows([row], checked)
          $xeTable.checkSelectionStatus()
          dispatchEvent('checkbox-change', Object.assign({
            records: () => $xeTable.getCheckboxRecords(),
            reserves: () => $xeTable.getCheckboxReserveRecords(),
            indeterminates: () => $xeTable.getCheckboxIndeterminateRecords(),
            checked
          }, params), evnt)
        }
      },
      /**
       * 多选，选中所有事件
       */
      triggerCheckAllEvent (evnt, value) {
        const checkboxOpts = computeCheckboxOpts.value
        const { trigger } = checkboxOpts
        if (trigger === 'manual') {
          return
        }
        if (evnt) {
          evnt.stopPropagation()
        }
        handleCheckAllEvent(evnt, value)
      },
      /**
       * 单选，行选中事件
       */
      triggerRadioRowEvent (evnt, params) {
        const { selectRadioRow: oldValue } = reactData
        const { row } = params
        const radioOpts = computeRadioOpts.value
        const { trigger, strict, checkMethod } = radioOpts
        if (trigger === 'manual') {
          return
        }
        evnt.stopPropagation()
        if (!checkMethod || checkMethod({ $table: $xeTable, row })) {
          let newValue = row
          let isChange = oldValue !== newValue
          if (strict) {
            handleCheckedRadioRow(newValue, false)
          } else {
            if (oldValue === newValue) {
              newValue = null
            }
            isChange = oldValue !== newValue
            if (isChange && newValue) {
              handleCheckedRadioRow(newValue, false)
            } else {
              newValue = null
              $xeTable.clearRadioRow()
            }
          }
          if (isChange) {
            dispatchEvent('radio-change', { oldValue, newValue, selected: !!newValue, ...params }, evnt)
          }
        }
      },
      triggerCurrentColumnEvent (evnt, params) {
        const { currentColumn: oldValue } = reactData
        const columnOpts = computeColumnOpts.value
        const currentColumnOpts = computeCurrentColumnOpts.value
        const beforeRowMethod = currentColumnOpts.beforeSelectMethod || columnOpts.currentMethod as any
        let newValue: VxeTableDefines.ColumnInfo | null = params.column
        const { trigger, strict } = currentColumnOpts
        if (trigger === 'manual') {
          return
        }
        let isChange = oldValue !== newValue
        if (!beforeRowMethod || beforeRowMethod({ column: newValue, $table: $xeTable })) {
          if (strict) {
            $xeTable.setCurrentColumn(newValue)
          } else {
            if (oldValue === newValue) {
              newValue = null
            }
            isChange = oldValue !== newValue
            if (isChange && newValue) {
              $xeTable.setCurrentColumn(newValue)
            } else {
              newValue = null
              $xeTable.clearCurrentColumn()
            }
          }
          if (isChange) {
            dispatchEvent('current-column-change', { oldValue, newValue, selected: !!newValue, ...params }, evnt)
          }
        } else {
          dispatchEvent('current-column-disabled', params, evnt)
        }
      },
      triggerCurrentRowEvent (evnt, params) {
        const { currentRow: oldValue } = internalData
        const rowOpts = computeRowOpts.value
        const currentRowOpts = computeCurrentRowOpts.value
        const beforeRowMethod = currentRowOpts.beforeSelectMethod || rowOpts.currentMethod as any
        let { row: newValue } = params
        const { trigger, strict } = currentRowOpts
        if (trigger === 'manual') {
          return
        }
        let isChange = oldValue !== newValue
        if (!beforeRowMethod || beforeRowMethod({ row: newValue, $table: $xeTable })) {
          if (strict) {
            $xeTable.setCurrentRow(newValue)
          } else {
            if (oldValue === newValue) {
              newValue = null
            }
            isChange = oldValue !== newValue
            if (isChange && newValue) {
              $xeTable.setCurrentRow(newValue)
            } else {
              newValue = null
              $xeTable.clearCurrentRow()
            }
          }
          if (isChange) {
            dispatchEvent('current-row-change', { oldValue, newValue, selected: !!newValue, ...params }, evnt)
            // 已废弃
            dispatchEvent('current-change', { oldValue, newValue, ...params }, evnt)
          }
        } else {
          dispatchEvent('current-row-disabled', params, evnt)
        }
      },
      /**
       * 展开行事件
       */
      triggerRowExpandEvent (evnt, params) {
        const { expandColumn } = reactData
        const { rowExpandLazyLoadedMaps } = internalData
        const expandOpts = computeExpandOpts.value
        const { row } = params
        const { lazy, trigger } = expandOpts
        if (trigger === 'manual') {
          return
        }
        evnt.stopPropagation()
        const rowid = getRowid($xeTable, row)
        if (!lazy || !rowExpandLazyLoadedMaps[rowid]) {
          const expanded = !$xeTable.isRowExpandByRow(row)
          const columnIndex = expandColumn ? $xeTable.getColumnIndex(expandColumn) : -1
          const $columnIndex = expandColumn ? $xeTable.getVMColumnIndex(expandColumn) : -1
          $xeTable.setRowExpand(row, expanded)
          dispatchEvent('toggle-row-expand', {
            expanded,
            column: expandColumn,
            columnIndex,
            $columnIndex,
            row,
            rowIndex: $xeTable.getRowIndex(row),
            $rowIndex: $xeTable.getVMRowIndex(row)
          }, evnt)
        }
      },
      /**
       * 行分组事件
       */
      triggerRowGroupExpandEvent (evnt, params) {
        const { rowGroupExpandedMaps } = internalData
        const aggregateOpts = computeAggregateOpts.value
        const { row, column } = params
        const { trigger } = aggregateOpts
        if (trigger === 'manual') {
          return
        }
        evnt.stopPropagation()
        const rowid = getRowid($xeTable, row)
        const expanded = !rowGroupExpandedMaps[rowid]
        const columnIndex = $xeTable.getColumnIndex(column)
        const $columnIndex = $xeTable.getVMColumnIndex(column)
        $xeTable.setRowGroupExpand(row, expanded)
        dispatchEvent('toggle-row-group-expand', { expanded, column, columnIndex, $columnIndex, row }, evnt)
      },
      /**
       * 展开树节点事件
       */
      triggerTreeExpandEvent (evnt, params) {
        const { treeExpandLazyLoadedMaps, treeEATime } = internalData
        const treeOpts = computeTreeOpts.value
        const { row, column } = params
        const { lazy, trigger, accordion } = treeOpts
        if (trigger === 'manual') {
          return
        }
        evnt.stopPropagation()
        const rowid = getRowid($xeTable, row)
        if (!lazy || !treeExpandLazyLoadedMaps[rowid]) {
          const expanded = !$xeTable.isTreeExpandByRow(row)
          const columnIndex = $xeTable.getColumnIndex(column)
          const $columnIndex = $xeTable.getVMColumnIndex(column)
          if (treeEATime) {
            clearTimeout(treeEATime)
          }
          $xeTable.setTreeExpand(row, expanded).then(() => {
            if (accordion) {
              internalData.treeEATime = setTimeout(() => {
                internalData.treeEATime = undefined
                $xeTable.scrollToRow(row)
              }, 30)
            }
          })
          dispatchEvent('toggle-tree-expand', { expanded, column, columnIndex, $columnIndex, row }, evnt)
        }
      },
      handleColumnSortEvent (evnt, column) {
        const { mouseConfig } = props
        const mouseOpts = computeMouseOpts.value
        const { field, sortable, order } = column
        if (sortable) {
          const params = { $table: $xeTable, $event: evnt, column, field, property: field, order, sortList: $xeTable.getSortColumns(), sortTime: column.sortTime }
          if (mouseConfig && mouseOpts.area && $xeTable.handleSortEvent) {
            $xeTable.handleSortEvent(evnt, params)
          }
          if (!order) {
            dispatchEvent('clear-sort', params, evnt)
          }
          dispatchEvent('sort-change', params, evnt)
        }
      },
      /**
       * 点击排序事件
       */
      triggerSortEvent (evnt, column, order) {
        const sortOpts = computeSortOpts.value
        const { multiple, allowClear } = sortOpts
        const { field, sortable } = column
        if (sortable) {
          if (!order || column.order === order) {
            if (allowClear) {
              $xeTable.clearSort(multiple ? column : null)
            }
          } else {
            $xeTable.sort({ field, order })
          }
          $xeTable.handleColumnSortEvent(evnt, column)
        }
      },
      handleCellRuleUpdateStatus (type, cellParams, cellValue) {
        const { validStore } = reactData
        const { row, column } = cellParams
        if ($xeTable.hasCellRules) {
          if ($xeTable.hasCellRules(type, row, column)) {
            const cell = $xeTable.getCellElement(row, column)
            if (cell) {
              const customVal = !XEUtils.isUndefined(cellValue)
              return $xeTable.validCellRules(type, row, column, cellValue)
                .then(() => {
                  if (customVal && validStore.visible) {
                    setCellValue(row, column, cellValue)
                  }
                  $xeTable.clearValidate(row, column)
                })
                .catch(({ rule }: any) => {
                  if (customVal) {
                    setCellValue(row, column, cellValue)
                  }
                  $xeTable.showValidTooltip({ rule, row, column, cell })
                })
            }
          }
        }
        return nextTick()
      },
      /**
       * 表头单元格按下事件
       */
      triggerHeaderCellMousedownEvent (evnt, params) {
        const { mouseConfig } = props
        const mouseOpts = computeMouseOpts.value
        const columnOpts = computeColumnOpts.value
        const columnDragOpts = computeColumnDragOpts.value
        const { trigger, isCrossDrag, isPeerDrag, disabledMethod } = columnDragOpts
        const cell = evnt.currentTarget as HTMLDivElement
        const triggerInput = cell && cell.tagName && cell.tagName.toLowerCase() === 'input'
        const triggerCheckbox = getEventTargetNode(evnt, cell, 'vxe-cell--checkbox').flag
        const triggerSort = getEventTargetNode(evnt, cell, 'vxe-cell--sort').flag
        const triggerFilter = getEventTargetNode(evnt, cell, 'vxe-cell--filter').flag
        let triggerDrag = false
        const isColDragCell = columnOpts.drag && trigger === 'cell'
        if (!(triggerInput || triggerCheckbox || triggerSort || triggerFilter)) {
          const { column } = params
          if (isColDragCell && !column.fixed && (isCrossDrag || isPeerDrag || !column.parentId) && !(disabledMethod && disabledMethod(params))) {
            triggerDrag = true
            $xeTable.handleHeaderCellDragMousedownEvent(evnt, params)
          }
        }
        if (!triggerDrag && mouseConfig && mouseOpts.area && $xeTable.handleHeaderCellAreaMouseDnEvent) {
          $xeTable.handleHeaderCellAreaMouseDnEvent(evnt, Object.assign({ cell, triggerSort, triggerFilter }, params))
        }
        $xeTable.focus()
        if ($xeTable.closeMenu) {
          $xeTable.closeMenu()
        }
      },
      /**
       * 单元格按下事件
       */
      triggerCellMousedownEvent (evnt, params) {
        const { column } = params
        const { type, treeNode } = column
        const isRadioType = type === 'radio'
        const isCheckboxType = type === 'checkbox'
        const isExpandType = type === 'expand'
        const rowOpts = computeRowOpts.value
        const rowDragOpts = computeRowDragOpts.value
        const { trigger, isCrossDrag, isPeerDrag, disabledMethod } = rowDragOpts
        const cell = evnt.currentTarget as HTMLElement
        params.cell = cell
        const triggerInput = cell && cell.tagName && cell.tagName.toLowerCase() === 'input'
        const triggerRadio = isRadioType && getEventTargetNode(evnt, cell, 'vxe-cell--radio').flag
        const triggerCheckbox = isCheckboxType && getEventTargetNode(evnt, cell, 'vxe-cell--checkbox').flag
        const triggerTreeNode = treeNode && getEventTargetNode(evnt, cell, 'vxe-cell--tree-btn').flag
        const triggerExpandNode = isExpandType && getEventTargetNode(evnt, cell, 'vxe-table--expanded').flag
        let isColDragCell = false
        if (rowOpts.drag) {
          isColDragCell = trigger === 'row' || (column.dragSort && trigger === 'cell')
        }
        let triggerDrag = false
        if (!(triggerInput || triggerRadio || triggerCheckbox || triggerTreeNode || triggerExpandNode)) {
          if (isColDragCell && (isCrossDrag || isPeerDrag || !params.level) && !(disabledMethod && disabledMethod(params))) {
            triggerDrag = true
            $xeTable.handleCellDragMousedownEvent(evnt, params)
          }
        }
        if (!triggerDrag && $xeTable.handleCellMousedownEvent) {
          $xeTable.handleCellMousedownEvent(evnt, params)
        }
        $xeTable.focus()
        $xeTable.closeFilter()
        if ($xeTable.closeMenu) {
          $xeTable.closeMenu()
        }
      },
      triggerCellMouseupEvent () {
        clearDragStatus()
      },
      /**
       * 行拖拽
       */
      handleRowDragDragstartEvent (evnt) {
        if (evnt.dataTransfer) {
          evnt.dataTransfer.setDragImage(getTpImg(), 0, 0)
        }
      },
      handleRowDragSwapEvent (evnt, isSyncRow, dragRow, prevDragRow, prevDragPos, prevDragToChild) {
        const { treeConfig, dragConfig } = props
        const rowDragOpts = computeRowDragOpts.value
        const { afterFullData, tableFullData, fullAllDataRowIdData } = internalData
        const $xeGanttView = internalData.xeGanttView
        const { animation, isPeerDrag, isCrossDrag, isSelfToChildDrag, dragEndMethod, dragToChildMethod } = rowDragOpts
        const treeOpts = computeTreeOpts.value
        const cellOpts = computeCellOpts.value
        const rowOpts = computeRowOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const { transform, rowField, mapChildrenField, parentField } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        const dEndMethod = dragEndMethod || (dragConfig ? dragConfig.dragEndMethod : null)
        const dragOffsetIndex = prevDragPos === 'bottom' ? 1 : 0
        const el = refElem.value
        const errRest = {
          status: false
        }
        if (!(el && prevDragRow && dragRow)) {
          return Promise.resolve(errRest)
        }
        // 判断是否有拖动
        if (prevDragRow !== dragRow) {
          const dragParams = {
            oldRow: dragRow,
            newRow: prevDragRow,
            dragRow,
            dragPos: prevDragPos as 'top' | 'bottom',
            dragToChild: !!prevDragToChild,
            offsetIndex: dragOffsetIndex as 0 | 1
          }

          const dragRowid = getRowid($xeTable, dragRow)
          const dragRowRest = fullAllDataRowIdData[dragRowid] || {}
          const _dragRowIndex = dragRowRest._index
          let dragRowHeight = 0
          let dragOffsetTop = -1
          if (animation) {
            dragRowHeight = getCellRestHeight(dragRowRest, cellOpts, rowOpts, defaultRowHeight)
            const oldTrEl = el.querySelector<HTMLElement>(`.vxe-body--row[rowid="${dragRowid}"]`)
            if (oldTrEl) {
              dragOffsetTop = oldTrEl.offsetTop
            }
          }

          let oafIndex = -1
          let nafIndex = -1
          const oldRest = dragRowRest
          const newRowid = getRowid($xeTable, prevDragRow)
          const newRest = fullAllDataRowIdData[newRowid]
          const oldAllMaps: Record<string, any> = {}
          let isSelfToChildStatus = false

          // 如果为树结构
          if (treeConfig) {
            if (transform) {
              if (oldRest && newRest) {
                const { level: oldLevel } = oldRest
                const { level: newLevel } = newRest

                XEUtils.eachTree([dragRow], item => {
                  oldAllMaps[getRowid($xeTable, item)] = item
                }, { children: mapChildrenField })

                if (oldLevel && newLevel) {
                  // 子到子

                  if (isPeerDrag && !isCrossDrag) {
                    if (oldRest.row[parentField] !== newRest.row[parentField]) {
                      // 非同级
                      handleRowDragEndClearStatus()
                      return Promise.resolve(errRest)
                    }
                  } else {
                    if (!isCrossDrag) {
                      handleRowDragEndClearStatus()
                      return Promise.resolve(errRest)
                    }
                    if (oldAllMaps[newRowid]) {
                      isSelfToChildStatus = true
                      if (!(isCrossDrag && isSelfToChildDrag)) {
                        if (VxeUI.modal) {
                          VxeUI.modal.message({
                            status: 'error',
                            content: getI18n('vxe.error.treeDragChild')
                          })
                        }
                        handleRowDragEndClearStatus()
                        return Promise.resolve(errRest)
                      }
                    }
                  }
                } else if (oldLevel) {
                  // 子到根

                  if (!isCrossDrag) {
                    handleRowDragEndClearStatus()
                    return Promise.resolve(errRest)
                  }
                } else if (newLevel) {
                  // 根到子

                  if (!isCrossDrag) {
                    handleRowDragEndClearStatus()
                    return Promise.resolve(errRest)
                  }
                  if (oldAllMaps[newRowid]) {
                    isSelfToChildStatus = true
                    if (!(isCrossDrag && isSelfToChildDrag)) {
                      if (VxeUI.modal) {
                        VxeUI.modal.message({
                          status: 'error',
                          content: getI18n('vxe.error.treeDragChild')
                        })
                      }
                      handleRowDragEndClearStatus()
                      return Promise.resolve(errRest)
                    }
                  }
                } else {
                  // 根到根
                }
              }
            }
          }

          const isDragToChildFlag = isSelfToChildDrag && dragToChildMethod ? dragToChildMethod(dragParams) : prevDragToChild
          return Promise.resolve(dEndMethod ? dEndMethod(dragParams) : true).then((status) => {
            if (!status) {
              return errRest
            }

            // 如果为树结构
            if (treeConfig) {
              if (transform) {
                // 移出源位置
                if (oldRest && newRest) {
                  const fullList = XEUtils.toTreeArray(internalData.afterTreeFullData, {
                    updated: false,
                    key: rowField,
                    parentKey: parentField,
                    children: mapChildrenField
                  })

                  // 移出
                  const otfIndex = $xeTable.findRowIndexOf(fullList, dragRow)
                  fullList.splice(otfIndex, 1)

                  // 插入
                  const ptfIndex = $xeTable.findRowIndexOf(fullList, prevDragRow)
                  const ntfIndex = ptfIndex + dragOffsetIndex
                  fullList.splice(ntfIndex, 0, dragRow)

                  // 改变层级
                  if (isSelfToChildStatus && (isCrossDrag && isSelfToChildDrag)) {
                    XEUtils.each(dragRow[childrenField], childRow => {
                      childRow[parentField] = dragRow[parentField]
                    })
                  }
                  dragRow[parentField] = isDragToChildFlag ? prevDragRow[rowField] : prevDragRow[parentField]

                  internalData.tableFullTreeData = XEUtils.toArrayTree(fullList, {
                    key: rowField,
                    parentKey: parentField,
                    children: childrenField,
                    mapChildren: mapChildrenField
                  })
                }
              }
            } else {
              // 移出
              oafIndex = $xeTable.findRowIndexOf(afterFullData, dragRow)
              const otfIndex = $xeTable.findRowIndexOf(tableFullData, dragRow)
              afterFullData.splice(oafIndex, 1)
              tableFullData.splice(otfIndex, 1)
              // 插入
              const pafIndex = $xeTable.findRowIndexOf(afterFullData, prevDragRow)
              const ptfIndex = $xeTable.findRowIndexOf(tableFullData, prevDragRow)
              nafIndex = pafIndex + dragOffsetIndex
              const ntfIndex = ptfIndex + dragOffsetIndex
              afterFullData.splice(nafIndex, 0, dragRow)
              tableFullData.splice(ntfIndex, 0, dragRow)
            }

            $xeTable.handleTableData(treeConfig && transform)
            $xeTable.cacheRowMap(false)
            updateScrollYStatus()
            if (!(treeConfig && transform)) {
              $xeTable.updateAfterDataIndex()
            }
            $xeTable.checkSelectionStatus()
            if (reactData.scrollYLoad) {
              $xeTable.updateScrollYSpace()
            }

            if (evnt) {
              dispatchEvent('row-dragend', {
                oldRow: dragRow,
                newRow: prevDragRow,
                dragRow,
                dragPos: prevDragPos as any,
                dragToChild: isDragToChildFlag,
                offsetIndex: dragOffsetIndex,
                _index: {
                  newIndex: nafIndex,
                  oldIndex: oafIndex
                }
              }, evnt)
            }

            return nextTick().then(() => {
              if (animation) {
                const { tableData } = reactData
                const { fullAllDataRowIdData } = internalData
                const dragRowRest = fullAllDataRowIdData[dragRowid]
                const _newRowIndex = dragRowRest._index
                const firstRow = tableData[0]
                const firstRowRest = fullAllDataRowIdData[getRowid($xeTable, firstRow)]
                let wrapperEl = el

                if ($xeGantt && $xeGanttView) {
                  const { refGanttContainerElem } = $xeGantt.getRefMaps()
                  const ganttContainerElem = refGanttContainerElem.value
                  if (ganttContainerElem) {
                    wrapperEl = ganttContainerElem
                  }
                }
                if (firstRowRest) {
                  const _firstRowIndex = firstRowRest._index
                  const _lastRowIndex = _firstRowIndex + tableData.length

                  let rsIndex = -1
                  let reIndex = -1
                  let offsetRate = 1
                  if (_dragRowIndex < _firstRowIndex) {
                    // 从上往下虚拟拖拽
                    rsIndex = 0
                    reIndex = _newRowIndex - _firstRowIndex
                  } else if (_dragRowIndex > _lastRowIndex) {
                    // 从下往上虚拟拖拽
                    const $newRowIndex = dragRowRest.$index
                    rsIndex = $newRowIndex + 1
                    reIndex = tableData.length
                    offsetRate = -1
                  } else {
                    if (_newRowIndex > _dragRowIndex) {
                      // 从上往下拖拽
                      rsIndex = _dragRowIndex - _firstRowIndex
                      reIndex = rsIndex + _newRowIndex - _dragRowIndex
                    } else {
                      // 从下往上拖拽
                      rsIndex = _newRowIndex - _firstRowIndex
                      reIndex = rsIndex + _dragRowIndex - _newRowIndex + 1
                      offsetRate = -1
                    }
                  }

                  const dragRangeList = tableData.slice(rsIndex, reIndex)
                  if (dragRangeList.length) {
                    const dtClss: string[] = []
                    dragRangeList.forEach(row => {
                      const rowid = getRowid($xeTable, row)
                      dtClss.push(`.vxe-body--row[rowid="${rowid}"]`)
                      if ($xeGantt) {
                        dtClss.push(`.vxe-gantt-view--body-row[rowid="${rowid}"]`, `.vxe-gantt-view--chart-row[rowid="${rowid}"]`)
                      }
                    })
                    const dtTrList = wrapperEl.querySelectorAll<HTMLElement>(dtClss.join(','))
                    moveRowAnimateToTb(dtTrList, offsetRate * dragRowHeight)
                  }
                }

                const drClss = [`.vxe-body--row[rowid="${dragRowid}"]`]
                if ($xeGantt) {
                  drClss.push(`.vxe-gantt-view--body-row[rowid="${dragRowid}"]`, `.vxe-gantt-view--chart-row[rowid="${dragRowid}"]`)
                }
                const newDtTrList = wrapperEl.querySelectorAll<HTMLElement>(drClss.join(','))
                const newTrEl = newDtTrList[0]
                if (dragOffsetTop > -1 && newTrEl) {
                  moveRowAnimateToTb(newDtTrList, dragOffsetTop - newTrEl.offsetTop)
                }
              }

              updateRowOffsetTop()
              updateRowExpandStyle()
              $xeTable.updateCellAreas()
              $xeTable.recalculate()
            }).then(() => {
              return {
                status: true
              }
            })
          }).catch(() => {
            return errRest
          }).then((rest) => {
            handleRowDragEndClearStatus()
            return rest
          })
        }
        handleRowDragEndClearStatus()
        return Promise.resolve(errRest)
      },
      handleCrossTableRowDragCancelEvent () {
        handleRowDragEndClearStatus()
      },
      /**
       * 处理跨表拖拽完成
       */
      handleCrossTableRowDragFinishEvent (evnt) {
        const { tableData } = reactData
        const { fullAllDataRowIdData } = internalData
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const rowDragOpts = computeRowDragOpts.value
        const { animation, isCrossTableDrag } = rowDragOpts
        const treeOpts = computeTreeOpts.value
        const { mapChildrenField } = treeOpts
        const el = refElem.value
        if (isCrossTableDrag && crossTableDragRowObj && crossTableDragRowInfo) {
          const { row: dragRow } = crossTableDragRowInfo
          if (dragRow) {
            const dragRowid = getRowid($xeTable, dragRow)
            const dragRowRest = fullAllDataRowIdData[dragRowid]
            let dragRowHeight = 0
            let rsIndex = -1
            if (dragRowRest) {
              if (animation) {
                dragRowHeight = getCellRestHeight(dragRowRest, cellOpts, rowOpts, defaultRowHeight)
              }
              rsIndex = dragRowRest.$index
            }
            const dragRangeList = rsIndex > -1 && rsIndex < tableData.length - 1 ? tableData.slice(rsIndex + 1) : []
            const dragList = XEUtils.toTreeArray([dragRow], {
              updated: true,
              children: mapChildrenField
            })
            $xeTable.remove(dragList).then(() => {
              if (animation && dragRowHeight && dragRangeList.length) {
                const $xeGanttView = internalData.xeGanttView
                let wrapperEl = el
                if ($xeGantt && $xeGanttView) {
                  const { refGanttContainerElem } = $xeGantt.getRefMaps()
                  const ganttContainerElem = refGanttContainerElem.value
                  if (ganttContainerElem) {
                    wrapperEl = ganttContainerElem
                  }
                }

                const dtClss: string[] = []
                dragRangeList.forEach(row => {
                  const rowid = getRowid($xeTable, row)
                  dtClss.push(`.vxe-body--row[rowid="${rowid}"]`)
                  if ($xeGantt) {
                    dtClss.push(`.vxe-gantt-view--body-row[rowid="${rowid}"]`, `.vxe-gantt-view--chart-row[rowid="${rowid}"]`)
                  }
                })
                const dtTrList = wrapperEl.querySelectorAll<HTMLElement>(dtClss.join(','))
                moveRowAnimateToTb(dtTrList, dragRowHeight)
              }
            })
            dispatchEvent('row-remove-dragend', {
              row: dragRow
            }, evnt)
            handleRowDragEndClearStatus()
          }
        }
      },
      /**
       * 处理跨表拖至新的空表
       */
      handleCrossTableRowDragoverEmptyEvent (evnt) {
        const { tableData } = reactData
        const rowDragOpts = computeRowDragOpts.value
        const { isCrossTableDrag } = rowDragOpts
        if (isCrossTableDrag && crossTableDragRowObj && !tableData.length) {
          const { $oldTable, $newTable } = crossTableDragRowObj
          if ($oldTable) {
            const oldTableReactData = $oldTable.reactData
            if ($oldTable.xID !== $xeTable.xID) {
              if ($newTable && $newTable.xID !== $xeTable.xID) {
                $newTable.hideCrossTableRowDropClearStatus()
              }
              evnt.preventDefault()
              $oldTable.hideCrossTableRowDropClearStatus()
              crossTableDragRowObj.$newTable = $xeTable
              internalData.prevDragRow = null
              reactData.dragTipText = oldTableReactData.dragTipText
              showDropTip(evnt, evnt.currentTarget as HTMLDivElement, null, true, '')
            }
          }
        }
      },
      /**
       * 处理跨表拖插入
       */
      handleCrossTableRowDragInsertEvent (evnt) {
        const { treeConfig } = props
        const { prevDragRow, prevDragPos, prevDragToChild } = internalData
        const rowDragOpts = computeRowDragOpts.value
        const { animation, isSelfToChildDrag, isCrossTableDrag, dragEndMethod, dragToChildMethod } = rowDragOpts
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const treeOpts = computeTreeOpts.value
        const { parentField, mapChildrenField } = treeOpts
        const childrenField = treeOpts.children || treeOpts.childrenField
        // 跨表拖拽
        if (isCrossTableDrag && crossTableDragRowObj && crossTableDragRowInfo) {
          const { row: oldRow } = crossTableDragRowInfo
          const { $oldTable } = crossTableDragRowObj
          const el = refElem.value
          if ($oldTable && oldRow) {
            const dragRow = oldRow
            let dragOffsetIndex = -1
            if (prevDragRow) {
              dragOffsetIndex = prevDragPos === 'bottom' ? 1 : 0
            }
            const dragParams = {
              oldRow: dragRow,
              newRow: prevDragRow,
              dragRow,
              dragPos: prevDragPos as 'top' | 'bottom',
              dragToChild: !!prevDragToChild,
              offsetIndex: dragOffsetIndex as 0 | 1
            }
            const isDragToChildFlag = isSelfToChildDrag && dragToChildMethod ? dragToChildMethod(dragParams) : prevDragToChild
            const errRest = {
              status: false
            }
            Promise.resolve(dragEndMethod ? dragEndMethod(dragParams) : true).then((status) => {
              if (!status) {
                if ($oldTable) {
                  if ($oldTable.xID !== $xeTable.xID) {
                    $oldTable.handleCrossTableRowDragCancelEvent(evnt)
                  }
                }
                handleRowDragEndClearStatus()
                return errRest
              }
              let insertRest: Promise<any> = Promise.resolve()
              if (treeConfig) {
                const dragList = XEUtils.toTreeArray([dragRow], {
                  updated: true,
                  children: mapChildrenField
                })
                $oldTable.handleCrossTableRowDragFinishEvent(evnt)
                if (prevDragRow) {
                  dragRow[parentField] = prevDragRow[parentField]
                } else {
                  dragRow[parentField] = null
                }
                dragList.forEach(row => {
                  row[childrenField] = undefined
                  row[mapChildrenField] = undefined
                })
                if (prevDragRow) {
                  if (prevDragPos === 'bottom') {
                    insertRest = $xeTable.insertNextAt(dragList, prevDragRow)
                  } else {
                    insertRest = $xeTable.insertAt(dragList, prevDragRow)
                  }
                } else {
                  insertRest = $xeTable.insert(dragList)
                }
              } else {
                $oldTable.handleCrossTableRowDragFinishEvent(evnt)
                if (prevDragRow) {
                  if (prevDragPos === 'bottom') {
                    insertRest = $xeTable.insertNextAt(dragRow, prevDragRow)
                  } else {
                    insertRest = $xeTable.insertAt(dragRow, prevDragRow)
                  }
                } else {
                  insertRest = $xeTable.insert(dragRow)
                }
              }
              dispatchEvent('row-insert-dragend', {
                oldRow,
                newRow: prevDragRow,
                dragRow,
                dragPos: prevDragPos as any,
                dragToChild: isDragToChildFlag,
                offsetIndex: dragOffsetIndex
              }, evnt)
              clearRowDragData()

              insertRest.then(() => {
                const { tableData } = reactData
                const { fullAllDataRowIdData } = internalData
                const oldRowid = getRowid($xeTable, dragRow)
                const oldRowRest = fullAllDataRowIdData[oldRowid]
                let dragRowHeight = 0
                let rsIndex = -1
                if (oldRowRest) {
                  if (animation) {
                    dragRowHeight = getCellRestHeight(oldRowRest, cellOpts, rowOpts, defaultRowHeight)
                  }
                  rsIndex = oldRowRest.$index
                }
                const dragRangeList = rsIndex > -1 ? tableData.slice(rsIndex) : []
                if (animation && dragRowHeight && dragRangeList.length) {
                  const $xeGanttView = internalData.xeGanttView
                  let wrapperEl = el
                  if ($xeGantt && $xeGanttView) {
                    const { refGanttContainerElem } = $xeGantt.getRefMaps()
                    const ganttContainerElem = refGanttContainerElem.value
                    if (ganttContainerElem) {
                      wrapperEl = ganttContainerElem
                    }
                  }

                  const dtClss: string[] = []
                  dragRangeList.forEach(row => {
                    const rowid = getRowid($xeTable, row)
                    dtClss.push(`.vxe-body--row[rowid="${rowid}"]`)
                    if ($xeGantt) {
                      dtClss.push(`.vxe-gantt-view--body-row[rowid="${rowid}"]`, `.vxe-gantt-view--chart-row[rowid="${rowid}"]`)
                    }
                  })
                  const dtTrList = wrapperEl.querySelectorAll<HTMLElement>(dtClss.join(','))
                  moveRowAnimateToTb(dtTrList, -dragRowHeight)
                }
              })
            })
          }
        }
      },
      hideCrossTableRowDropClearStatus () {
        hideDropTip()
      },
      handleRowDragDragendEvent (evnt) {
        const { treeConfig } = props
        const { fullAllDataRowIdData, prevDragToChild } = internalData
        const { dragRow } = reactData
        const treeOpts = computeTreeOpts.value
        const { lazy } = treeOpts
        const hasChildField = treeOpts.hasChild || treeOpts.hasChildField
        const { prevDragRow, prevDragPos } = internalData
        const rowDragOpts = computeRowDragOpts.value
        const { isCrossTableDrag } = rowDragOpts
        // 跨表拖拽
        if (isCrossTableDrag && crossTableDragRowObj) {
          const { $newTable } = crossTableDragRowObj
          if ($newTable && $newTable.xID !== $xeTable.xID) {
            $newTable.handleCrossTableRowDragInsertEvent(evnt)
            return
          }
        }
        if (treeConfig && lazy && prevDragToChild) {
          // 懒加载
          const newRowid = getRowid($xeTable, prevDragRow)
          const rowRest = fullAllDataRowIdData[newRowid]
          if (prevDragRow[hasChildField]) {
            if (rowRest && rowRest.treeLoaded) {
              $xeTable.handleRowDragSwapEvent(evnt, true, dragRow, prevDragRow, prevDragPos, prevDragToChild)
            }
          } else {
            $xeTable.handleRowDragSwapEvent(evnt, true, dragRow, prevDragRow, prevDragPos, prevDragToChild)
          }
        } else {
          $xeTable.handleRowDragSwapEvent(evnt, true, dragRow, prevDragRow, prevDragPos, prevDragToChild)
        }
      },
      handleRowDragDragoverEvent (evnt) {
        const { treeConfig } = props
        const { fullAllDataRowIdData } = internalData
        const { dragRow } = reactData
        const treeOpts = computeTreeOpts.value
        const { lazy, transform, parentField } = treeOpts
        const hasChildField = treeOpts.hasChild || treeOpts.hasChildField
        const rowDragOpts = computeRowDragOpts.value
        const { isPeerDrag, isCrossDrag, isToChildDrag, isCrossTableDrag } = rowDragOpts
        if (!dragRow && !(isCrossTableDrag && (!treeConfig || isCrossDrag) && crossTableDragRowObj)) {
          evnt.preventDefault()
          return
        }
        const isControlKey = hasControlKey(evnt)
        const trEl = evnt.currentTarget as HTMLElement
        const rowid = trEl.getAttribute('rowid') || ''
        const rowRest = fullAllDataRowIdData[rowid]
        if (rowRest) {
          evnt.preventDefault()
          const row = rowRest.row
          const offsetY = evnt.clientY - trEl.getBoundingClientRect().y
          const dragPos = offsetY < trEl.clientHeight / 2 ? 'top' : 'bottom'
          internalData.prevDragToChild = !!(treeConfig && transform && (isCrossDrag && isToChildDrag) && isControlKey)
          internalData.prevDragRow = row
          internalData.prevDragPos = dragPos
          // 跨表拖拽
          if (isCrossTableDrag && (!treeConfig || isCrossDrag) && crossTableDragRowObj) {
            const { $oldTable, $newTable } = crossTableDragRowObj
            if ($oldTable) {
              const oldTableReactData = $oldTable.reactData
              if ($oldTable.xID === $xeTable.xID) {
                if ($newTable) {
                  $newTable.hideCrossTableRowDropClearStatus()
                }
                reactData.isCrossDragRow = false
                oldTableReactData.isCrossDragRow = false
                crossTableDragRowObj.$newTable = null
              } else {
                if ($newTable && $newTable.xID !== $xeTable.xID) {
                  $newTable.hideCrossTableRowDropClearStatus()
                }
                $oldTable.hideCrossTableRowDropClearStatus()
                oldTableReactData.isCrossDragRow = true
                reactData.dragTipText = oldTableReactData.dragTipText
                crossTableDragRowObj.$newTable = $xeTable
                showDropTip(evnt, trEl, null, true, dragPos)
                return
              }
            }
          }
          if ($xeTable.eqRow(dragRow, row) ||
            (isControlKey && treeConfig && lazy && row[hasChildField] && rowRest && !rowRest.treeLoaded) ||
            (!isCrossDrag && treeConfig && transform && (isPeerDrag ? dragRow[parentField] !== row[parentField] : rowRest.level))
          ) {
            showDropTip(evnt, trEl, null, false, dragPos)
            return
          }
          showDropTip(evnt, trEl, null, true, dragPos)
          dispatchEvent('row-dragover', {
            oldRow: dragRow,
            targetRow: row,
            dragPos
          }, evnt)
        }
      },
      handleCellDragMousedownEvent (evnt, params) {
        evnt.stopPropagation()
        const { dragConfig } = props
        const rowDragOpts = computeRowDragOpts.value
        const { isCrossTableDrag, trigger, dragStartMethod } = rowDragOpts
        const { row } = params
        const dragEl = evnt.currentTarget as HTMLElement
        const tdEl = trigger === 'cell' || trigger === 'row' ? dragEl : dragEl.parentElement?.parentElement as HTMLElement
        const trEl = tdEl.parentElement as HTMLElement
        const dStartMethod = dragStartMethod || (dragConfig ? dragConfig.dragStartMethod : null)
        clearRowDropOrigin()
        if (dStartMethod && !dStartMethod(params)) {
          trEl.draggable = false
          reactData.dragRow = null
          reactData.dragCol = null
          clearCrossTableDragStatus()
          hideDropTip()
          return
        }
        if (isCrossTableDrag) {
          crossTableDragRowInfo.row = row
          crossTableDragRowObj = { $oldTable: $xeTable, $newTable: null }
        }
        reactData.dragRow = row
        reactData.isCrossDragRow = false
        reactData.dragCol = null
        trEl.draggable = true
        updateRowDropOrigin(row)
        updateRowDropTipContent(tdEl)
        dispatchEvent('row-dragstart', params, evnt)
      },
      handleCellDragMouseupEvent () {
        clearDragStatus()
      },
      /**
       * 列拖拽
       */
      handleHeaderCellDragDragstartEvent (evnt) {
        if (evnt.dataTransfer) {
          evnt.dataTransfer.setDragImage(getTpImg(), 0, 0)
        }
      },
      handleColDragSwapColumn () {
        handleUpdateColumn()
        return parseColumns(false).then(() => {
          $xeTable.updateCellAreas()
          $xeTable.saveCustomStore('update:sort')
        })
      },
      handleColDragSwapEvent (evnt, isSyncColumn, dragCol, prevDragCol, prevDragPos, prevDragToChild) {
        const { mouseConfig } = props
        const columnDragOpts = computeColumnDragOpts.value
        const { animation, isPeerDrag, isCrossDrag, isSelfToChildDrag, isToChildDrag, dragEndMethod, dragToChildMethod } = columnDragOpts
        const { collectColumn, fullColumnIdData } = internalData
        const el = refElem.value
        const dragOffsetIndex = prevDragPos === 'right' ? 1 : 0
        const errRest = {
          status: false
        }
        if (!(el && prevDragCol && dragCol)) {
          return Promise.resolve(errRest)
        }
        // 判断是否有拖动
        if (prevDragCol !== dragCol) {
          const dragColumn = dragCol
          const newColumn = prevDragCol
          const dragParams = {
            oldColumn: dragColumn,
            newColumn,
            dragColumn,
            dragPos: prevDragPos as 'left' | 'right',
            dragToChild: !!prevDragToChild,
            offsetIndex: dragOffsetIndex as 0 | 1
          }

          let dragTargetColumn: VxeTableDefines.ColumnInfo | null = null
          const dragAllTargetCols: VxeTableDefines.ColumnInfo[] = []
          let dragColWidth = 0
          if (animation) {
            XEUtils.eachTree([dragColumn], column => {
              if (!dragTargetColumn && (!column.children || !column.children.length)) {
                dragTargetColumn = column
                dragColWidth += column.renderWidth
              }
              dragAllTargetCols.push(column)
            })
          }
          if (!dragTargetColumn) {
            dragTargetColumn = dragColumn
          }
          const dragColRest = fullColumnIdData[dragTargetColumn.id] || {}
          const _dragColIndex = dragColRest._index

          let dragOffsetLeft = -1
          if (animation) {
            const oldTrEl = el.querySelector<HTMLElement>(`.vxe-table--column[colid="${dragTargetColumn.id}"]`)
            if (oldTrEl) {
              dragOffsetLeft = oldTrEl.offsetLeft
            }
          }

          let oafIndex = -1
          let nafIndex = -1

          const oldAllMaps: Record<string, any> = {}
          XEUtils.eachTree([dragColumn], column => {
            oldAllMaps[column.id] = column
          })

          let isSelfToChildStatus = false

          if (dragColumn.parentId && newColumn.parentId) {
            // 子到子

            if (isPeerDrag && !isCrossDrag) {
              if (dragColumn.parentId !== newColumn.parentId) {
                // 非同级
                handleColDragEndClearStatus()
                return Promise.resolve(errRest)
              }
            } else {
              if (!isCrossDrag) {
                handleColDragEndClearStatus()
                return Promise.resolve(errRest)
              }

              if (oldAllMaps[newColumn.id]) {
                isSelfToChildStatus = true
                if (!(isCrossDrag && isSelfToChildDrag)) {
                  if (VxeUI.modal) {
                    VxeUI.modal.message({
                      status: 'error',
                      content: getI18n('vxe.error.treeDragChild')
                    })
                  }
                  handleColDragEndClearStatus()
                  return Promise.resolve(errRest)
                }
              }
            }
          } else if (dragColumn.parentId) {
            // 子到根

            if (!isCrossDrag) {
              handleColDragEndClearStatus()
              return Promise.resolve(errRest)
            }
          } else if (newColumn.parentId) {
            // 根到子

            if (!isCrossDrag) {
              handleColDragEndClearStatus()
              return Promise.resolve(errRest)
            }
            if (oldAllMaps[newColumn.id]) {
              isSelfToChildStatus = true
              if (!(isCrossDrag && isSelfToChildDrag)) {
                if (VxeUI.modal) {
                  VxeUI.modal.message({
                    status: 'error',
                    content: getI18n('vxe.error.treeDragChild')
                  })
                }
                handleColDragEndClearStatus()
                return Promise.resolve(errRest)
              }
            }
          } else {
            // 根到根
          }

          const isDragToChildFlag = isSelfToChildDrag && dragToChildMethod ? dragToChildMethod(dragParams) : prevDragToChild
          return Promise.resolve(dragEndMethod ? dragEndMethod(dragParams) : true).then((status) => {
            if (!status) {
              return errRest
            }

            const oldewMatchRest = XEUtils.findTree(collectColumn, item => item.id === dragColumn.id)

            // 改变层级
            if (isSelfToChildStatus && (isCrossDrag && isSelfToChildDrag)) {
              if (oldewMatchRest) {
                const { items: oCols, index: oIndex } = oldewMatchRest
                const childList = dragColumn.children || []
                childList.forEach(column => {
                  column.parentId = dragColumn.parentId
                })
                oCols.splice(oIndex, 1, ...childList)
                dragColumn.children = []
              }
            } else {
              if (oldewMatchRest) {
                const { items: oCols, index: oIndex, parent: oParent } = oldewMatchRest
                oCols.splice(oIndex, 1)
                if (!oParent) {
                  oafIndex = oIndex
                }
              }
            }

            const newMatchRest = XEUtils.findTree(collectColumn, item => item.id === newColumn.id)
            if (newMatchRest) {
              const { items: nCols, index: nIndex, parent: nParent } = newMatchRest
              // 转子级
              if ((isCrossDrag && isToChildDrag) && isDragToChildFlag) {
                dragColumn.parentId = newColumn.id
                newColumn.children = (newColumn.children || []).concat([dragColumn])
              } else {
                dragColumn.parentId = newColumn.parentId
                nCols.splice(nIndex + dragOffsetIndex, 0, dragColumn)
              }
              if (!nParent) {
                nafIndex = nIndex
              }
            }

            XEUtils.eachTree(collectColumn, (column, index, items, path, parentColumn) => {
              if (!parentColumn) {
                const sortIndex = index + 1
                column.renderSortNumber = sortIndex
              }
            })

            if (mouseConfig) {
              if ($xeTable.clearSelected) {
                $xeTable.clearSelected()
              }
              if ($xeTable.clearCellAreas) {
                $xeTable.clearCellAreas()
                $xeTable.clearCopyCellArea()
              }
            }

            if (evnt) {
              dispatchEvent('column-dragend', {
                oldColumn: dragColumn,
                newColumn,
                dragColumn,
                dragPos: prevDragPos,
                dragToChild: isDragToChildFlag,
                offsetIndex: dragOffsetIndex,
                _index: {
                  newIndex: nafIndex,
                  oldIndex: oafIndex
                }
              }, evnt)
            }

            return nextTick().then(() => {
              if (isSyncColumn) {
                return $xeTable.handleColDragSwapColumn()
              }
            }).then(() => {
              if (animation) {
                const { tableColumn } = reactData
                const { visibleColumn, fullColumnIdData } = internalData
                let dragNewColumn: VxeTableDefines.ColumnInfo | null = null
                const dragNewColMaps: Record<string, VxeTableDefines.ColumnInfo> = {}
                XEUtils.eachTree([dragColumn], column => {
                  if (!dragNewColumn && (!column.children || !column.children.length)) {
                    dragNewColumn = column
                  }
                  dragNewColMaps[column.id] = column
                })
                if (!dragNewColumn) {
                  dragNewColumn = dragColumn
                }

                if (dragColWidth && dragAllTargetCols.length) {
                  const _newColIndex = XEUtils.findIndexOf(visibleColumn, column => !!dragNewColumn && column.id === dragNewColumn.id)
                  const firstCol = tableColumn[0]
                  const firstColRest = fullColumnIdData[firstCol.id]

                  if (firstColRest) {
                    const _firstColIndex = firstColRest._index
                    const _lastColIndex = _firstColIndex + tableColumn.length
                    let csIndex = -1
                    let ceIndex = -1
                    let offsetRate = 1
                    if (_dragColIndex < _firstColIndex) {
                      // 从左往右虚拟拖拽
                      csIndex = 0
                      ceIndex = _newColIndex - _firstColIndex
                    } else if (_dragColIndex > _lastColIndex) {
                      // 从右往左虚拟拖拽
                      const $newRowIndex = dragColRest.$index
                      csIndex = $newRowIndex + 1
                      ceIndex = tableColumn.length
                      offsetRate = -1
                    } else {
                      if (_newColIndex > _dragColIndex) {
                        // 从左往右拖拽
                        csIndex = _dragColIndex - _firstColIndex
                        ceIndex = csIndex + _newColIndex - _dragColIndex
                      } else {
                        // 从右往左拖拽
                        csIndex = _newColIndex - _firstColIndex + 1
                        ceIndex = csIndex + _dragColIndex - _newColIndex
                        offsetRate = -1
                      }
                    }

                    const dragRangeList: VxeTableDefines.ColumnInfo[] = []
                    const dragRangeMaps: Record<string, VxeTableDefines.ColumnInfo> = {}
                    for (let i = csIndex; i < ceIndex; i++) {
                      const column = tableColumn[i]
                      if (!dragRangeMaps[column.id] && !dragNewColMaps[column.id]) {
                        dragRangeMaps[column.id] = column
                        dragRangeList.push(column)
                      }
                    }
                    XEUtils.eachTree([newColumn], column => {
                      if (!dragRangeMaps[column.id]) {
                        dragRangeMaps[column.id] = column
                        dragRangeList.push(column)
                      }
                    })
                    if (dragRangeList.length) {
                      const dtTrList = el.querySelectorAll<HTMLElement>(dragRangeList.map(column => `.vxe-table--column[colid="${column.id}"]`).join(','))
                      moveColAnimateToLr(dtTrList, offsetRate * dragColWidth)
                    }
                  }

                  const newTrList = el.querySelectorAll<HTMLElement>(dragAllTargetCols.map(column => `.vxe-table--column[colid="${column.id}"]`).join(','))
                  const newTdEl = newTrList[0]
                  if (dragOffsetLeft > -1 && newTdEl) {
                    moveColAnimateToLr(newTrList, dragOffsetLeft - newTdEl.offsetLeft)
                  }
                }
              }

              updateColumnOffsetLeft()
              loadScrollXData()
              $xeTable.updateCellAreas()

              return {
                status: true
              }
            })
          }).catch(() => {
            return errRest
          }).then((rest) => {
            handleColDragEndClearStatus()
            return rest
          })
        }
        handleColDragEndClearStatus()
        return Promise.resolve(errRest)
      },
      handleHeaderCellDragDragendEvent (evnt) {
        const { dragCol } = reactData
        const { prevDragCol, prevDragPos, prevDragToChild } = internalData
        $xeTable.handleColDragSwapEvent(evnt, true, dragCol, prevDragCol, prevDragPos, prevDragToChild)
      },
      handleHeaderCellDragDragoverEvent (evnt) {
        const { dragCol } = reactData
        const columnDragOpts = computeColumnDragOpts.value
        const { isToChildDrag, isPeerDrag, isCrossDrag } = columnDragOpts
        if (!dragCol) {
          evnt.preventDefault()
          return
        }
        const isControlKey = hasControlKey(evnt)
        const thEl = evnt.currentTarget as HTMLElement
        const colid = thEl.getAttribute('colid')
        const column = $xeTable.getColumnById(colid)
        if (column) {
          evnt.preventDefault()
          const { clientX } = evnt
          const offsetX = clientX - thEl.getBoundingClientRect().x
          const dragPos = offsetX < thEl.clientWidth / 2 ? 'left' : 'right'
          internalData.prevDragToChild = !!((isCrossDrag && isToChildDrag) && isControlKey)
          internalData.prevDragCol = column
          internalData.prevDragPos = dragPos
          if (column.fixed ||
            (dragCol && dragCol.id === column.id) ||
            (!isCrossDrag && (isPeerDrag ? dragCol.parentId !== column.parentId : column.parentId))
          ) {
            showDropTip(evnt, null, thEl, false, dragPos)
            return
          }
          showDropTip(evnt, null, thEl, true, dragPos)
          dispatchEvent('column-dragover', {
            oldColumn: dragCol,
            targetColumn: column,
            dragPos
          }, evnt)

          // 边缘滚动
          const el = refElem.value
          if (!el) {
            return
          }
          const xHandleEl = refScrollXHandleElem.value
          const tableBody = refTableBody.value
          const tableBodyElem = tableBody ? tableBody.$el as HTMLDivElement : null
          const scrollTargetEl = xHandleEl || tableBodyElem
          if (scrollTargetEl) {
            const wrapperRect = el.getBoundingClientRect()
            const tableWrapperWidth = el.clientWidth
            const leftContainerElem = refLeftContainer.value
            const leftContainerWidth = leftContainerElem ? leftContainerElem.clientWidth : 0
            const rightContainerElem = refRightContainer.value
            const rightContainerWidth = rightContainerElem ? rightContainerElem.clientWidth : 0
            const srartX = wrapperRect.x + leftContainerWidth
            const endX = wrapperRect.x + tableWrapperWidth - rightContainerWidth
            const distSize = 28
            const startDistSize = clientX - srartX
            const endDistSize = endX - clientX
            if (startDistSize > 0 && startDistSize <= distSize) {
              const scrollRatio = Math.floor(tableWrapperWidth / (startDistSize > distSize / 2 ? 240 : 120))
              scrollTargetEl.scrollLeft -= scrollRatio * (distSize - startDistSize)
            } else if (endDistSize > 0 && endDistSize <= distSize) {
              const scrollRatio = Math.floor(tableWrapperWidth / (endDistSize > distSize / 2 ? 240 : 120))
              scrollTargetEl.scrollLeft += scrollRatio * (distSize - endDistSize)
            }
          }
        }
      },
      handleHeaderCellDragMousedownEvent (evnt, params) {
        evnt.stopPropagation()
        const columnDragOpts = computeColumnDragOpts.value
        const { trigger, dragStartMethod } = columnDragOpts
        const { column } = params
        const dragEl = evnt.currentTarget as HTMLElement
        const thEl = trigger === 'cell' ? dragEl : dragEl.parentElement?.parentElement as HTMLElement
        clearColDropOrigin()
        if (dragStartMethod && !dragStartMethod(params)) {
          thEl.draggable = false
          reactData.dragRow = null
          reactData.dragCol = null
          clearCrossTableDragStatus()
          hideDropTip()
          return
        }
        reactData.dragCol = column
        reactData.dragRow = null
        thEl.draggable = true
        clearCrossTableDragStatus()
        updateColDropOrigin(column)
        updateColDropTipContent(thEl)
        dispatchEvent('column-dragstart', params, evnt)
      },
      handleHeaderCellDragMouseupEvent () {
        clearColDropOrigin()
        hideDropTip()
        clearCrossTableDragStatus()
        reactData.dragRow = null
        reactData.dragCol = null
      },
      handleScrollEvent (evnt, isRollY, isRollX, scrollTop, scrollLeft, params) {
        const { highlightHoverRow } = props
        const { lastScrollLeft, lastScrollTop } = internalData
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value
        if (!xHandleEl || !yHandleEl) {
          return
        }
        const rowOpts = computeRowOpts.value
        const validTip = refValidTooltip.value
        const tooltip = refTooltip.value
        const bodyHeight = yHandleEl.clientHeight
        const bodyWidth = xHandleEl.clientWidth
        const scrollHeight = yHandleEl.scrollHeight
        const scrollWidth = xHandleEl.scrollWidth
        let isTop = false
        let isBottom = false
        let isLeft = false
        let isRight = false
        let direction = ''
        let isTopBoundary = false
        let isBottomBoundary = false
        let isLeftBoundary = false
        let isRightBoundary = false
        if (isRollX) {
          const xThreshold = computeScrollXThreshold.value
          isLeft = scrollLeft <= 0
          if (!isLeft) {
            isRight = scrollLeft + bodyWidth >= scrollWidth
          }
          if (scrollLeft > lastScrollLeft) {
            direction = 'right'
            if (scrollLeft + bodyWidth >= scrollWidth - xThreshold) {
              isRightBoundary = true
            }
          } else {
            direction = 'left'
            if (scrollLeft <= xThreshold) {
              isLeftBoundary = true
            }
          }
          $xeTable.checkScrolling()
          internalData.lastScrollLeft = scrollLeft
        }
        if (isRollY) {
          const yThreshold = computeScrollYThreshold.value
          isTop = scrollTop <= 1
          if (!isTop) {
            isBottom = scrollTop + bodyHeight >= scrollHeight - 1
          }
          if (scrollTop > lastScrollTop) {
            direction = 'bottom'
            if (scrollTop + bodyHeight >= scrollHeight - yThreshold) {
              isBottomBoundary = true
            }
          } else {
            direction = 'top'
            if (scrollTop <= yThreshold) {
              isTopBoundary = true
            }
          }
          internalData.lastScrollTop = scrollTop
        }
        internalData.lastSTime = Date.now()
        const evntParams = {
          source: sourceType,
          scrollTop,
          scrollLeft,
          bodyHeight,
          bodyWidth,
          scrollHeight,
          scrollWidth,
          isX: isRollX,
          isY: isRollY,
          isTop,
          isBottom,
          isLeft,
          isRight,
          direction,
          ...params
        }
        updateRowExpandStyle()
        checkLastSyncScroll(isRollX, isRollY)
        if (isRollX) {
          $xeTable.closeFilter()
        }
        if (rowOpts.isHover || highlightHoverRow) {
          $xeTable.clearHoverRow()
        }
        if (validTip && validTip.reactData.visible) {
          validTip.close()
        }
        if (tooltip && tooltip.reactData.visible) {
          tooltip.close()
        }
        if (isBottomBoundary || isTopBoundary || isRightBoundary || isLeftBoundary) {
          dispatchEvent('scroll-boundary', evntParams, evnt)
        }
        dispatchEvent('scroll', evntParams, evnt)
      },
      /**
       * 横向 X 可视渲染事件处理
       */
      triggerScrollXEvent () {
        const virtualXOpts = computeVirtualXOpts.value
        if (virtualXOpts.immediate) {
          loadScrollXData()
        } else {
          lazyScrollXData()
        }
      },
      /**
       * 纵向 Y 可视渲染事件处理
       */
      triggerScrollYEvent () {
        const virtualYOpts = computeVirtualYOpts.value
        if (virtualYOpts.immediate) {
          loadScrollYData()
        } else {
          lazyScrollYData()
        }
      },
      triggerBodyScrollEvent (evnt, fixedType) {
        const { scrollYLoad, scrollXLoad } = reactData
        const { elemStore, intoRunScroll, lastScrollTop, lastScrollLeft, inWheelScroll, inVirtualScroll, inHeaderScroll, inBodyScroll, scrollRenderType, inFooterScroll } = internalData
        if (inWheelScroll || inVirtualScroll || inHeaderScroll || inFooterScroll) {
          return
        }
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const rowExpandEl = refRowExpandElem.value
        if (intoRunScroll) {
          return
        }
        if (!bodyScrollElem) {
          return
        }
        if (!xHandleEl) {
          return
        }
        if (!yHandleEl) {
          return
        }
        if (inBodyScroll) {
          if (scrollRenderType !== fixedType) {
            return
          }
        }

        let scrollTop = yHandleEl.scrollTop
        let scrollLeft = xHandleEl.scrollLeft
        if (leftScrollElem && fixedType === 'left') {
          scrollTop = leftScrollElem.scrollTop
        } else if (rightScrollElem && fixedType === 'right') {
          scrollTop = rightScrollElem.scrollTop
        } else {
          scrollTop = bodyScrollElem.scrollTop
          scrollLeft = bodyScrollElem.scrollLeft
        }
        const isRollX = scrollLeft !== lastScrollLeft
        const isRollY = scrollTop !== lastScrollTop
        internalData.inBodyScroll = true
        internalData.scrollRenderType = fixedType
        if (isRollY) {
          if (fixedType === 'left') {
            setScrollTop(bodyScrollElem, scrollTop)
            setScrollTop(rightScrollElem, scrollTop)
          } else if (fixedType === 'right') {
            setScrollTop(bodyScrollElem, scrollTop)
            setScrollTop(leftScrollElem, scrollTop)
          } else {
            setScrollTop(leftScrollElem, scrollTop)
            setScrollTop(rightScrollElem, scrollTop)
          }
          setScrollTop(yHandleEl, scrollTop)
          setScrollTop(rowExpandEl, scrollTop)
          syncGanttScrollTop(scrollTop)
          if (scrollYLoad) {
            $xeTable.triggerScrollYEvent(evnt)
          }
        }
        if (isRollX) {
          setScrollLeft(xHandleEl, scrollLeft)
          setScrollLeft(headerScrollElem, scrollLeft)
          setScrollLeft(footerScrollElem, scrollLeft)
          if (scrollXLoad) {
            $xeTable.triggerScrollXEvent(evnt)
          }
        }
        $xeTable.handleScrollEvent(evnt, isRollY, isRollX, scrollTop, scrollLeft, {
          type: 'body',
          fixed: fixedType
        })
      },
      triggerHeaderScrollEvent (evnt, fixedType) {
        const { scrollXLoad } = reactData
        const { elemStore, intoRunScroll, inWheelScroll, inVirtualScroll, inBodyScroll, inFooterScroll } = internalData
        if (inWheelScroll || inVirtualScroll || inBodyScroll || inFooterScroll) {
          return
        }
        const yHandleEl = refScrollYHandleElem.value
        const xHandleEl = refScrollXHandleElem.value
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        if (intoRunScroll) {
          return
        }
        if (!headerScrollElem) {
          return
        }
        if (!xHandleEl) {
          return
        }
        if (!yHandleEl) {
          return
        }
        const scrollTop = yHandleEl.scrollTop
        const scrollLeft = headerScrollElem.scrollLeft
        const isRollX = true
        const isRollY = false
        internalData.inHeaderScroll = true
        setScrollLeft(xHandleEl, scrollLeft)
        setScrollLeft(footerScrollElem, scrollLeft)
        setScrollLeft(bodyScrollElem, scrollLeft)
        if (scrollXLoad) {
          $xeTable.triggerScrollXEvent(evnt)
        }
        $xeTable.handleScrollEvent(evnt, isRollY, isRollX, scrollTop, scrollLeft, {
          type: 'header',
          fixed: fixedType
        })
      },
      triggerFooterScrollEvent (evnt, fixedType) {
        const { scrollXLoad } = reactData
        const { elemStore, intoRunScroll, inWheelScroll, inVirtualScroll, inHeaderScroll, inBodyScroll } = internalData
        if (inWheelScroll || inVirtualScroll || inHeaderScroll || inBodyScroll) {
          return
        }
        const yHandleEl = refScrollYHandleElem.value
        const xHandleEl = refScrollXHandleElem.value
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        if (intoRunScroll) {
          return
        }
        if (!footerScrollElem) {
          return
        }
        if (!xHandleEl) {
          return
        }
        if (!yHandleEl) {
          return
        }
        const scrollTop = yHandleEl.scrollTop
        const scrollLeft = footerScrollElem.scrollLeft
        const isRollX = true
        const isRollY = false
        internalData.inFooterScroll = true
        setScrollLeft(xHandleEl, scrollLeft)
        setScrollLeft(headerScrollElem, scrollLeft)
        setScrollLeft(bodyScrollElem, scrollLeft)
        if (scrollXLoad) {
          $xeTable.triggerScrollXEvent(evnt)
        }
        $xeTable.handleScrollEvent(evnt, isRollY, isRollX, scrollTop, scrollLeft, {
          type: 'footer',
          fixed: fixedType
        })
      },
      triggerBodyWheelEvent (evnt) {
        const { target, deltaY, deltaX, shiftKey } = evnt
        if (target && /^textarea$/i.test((target as HTMLElement).tagName)) {
          return
        }
        // 如果滚轮未移动或者触摸板未变化位置
        if (!deltaY && !deltaX) {
          return
        }

        const { highlightHoverRow } = tableProps
        const { scrollXLoad, scrollYLoad, expandColumn } = reactData
        const leftFixedWidth = computeLeftFixedWidth.value
        const rightFixedWidth = computeRightFixedWidth.value

        const { elemStore, lastScrollTop, lastScrollLeft } = internalData
        const rowOpts = computeRowOpts.value
        const scrollbarXOpts = computeScrollbarXOpts.value
        const scrollbarYOpts = computeScrollbarYOpts.value
        const xHandleEl = refScrollXHandleElem.value
        const yHandleEl = refScrollYHandleElem.value
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const rowExpandEl = refRowExpandElem.value
        if (!bodyScrollElem) {
          return
        }

        const wheelSpeed = getWheelSpeed(internalData.lastSTime)
        const deltaTop = shiftKey ? 0 : (deltaY * wheelSpeed)
        const deltaLeft = (shiftKey ? (deltaX || deltaY) : deltaX) * wheelSpeed

        const currScrollTop = bodyScrollElem.scrollTop
        const currScrollLeft = bodyScrollElem.scrollLeft

        const scrollTop = currScrollTop + deltaTop
        const scrollLeft = currScrollLeft + deltaLeft
        const isRollX = scrollLeft !== lastScrollLeft
        const isRollY = scrollTop !== lastScrollTop

        if (isRollX) {
          // 如果禁用滚动
          if (scrollbarXOpts.visible === 'hidden') {
            evnt.preventDefault()
            return
          }
        }
        if (isRollY) {
          // 如果禁用滚动
          if (scrollbarYOpts.visible === 'hidden') {
            evnt.preventDefault()
            return
          }
          const isTopWheel = deltaTop < 0
          // 如果滚动位置已经是顶部或底部，则不需要触发
          if (isTopWheel ? currScrollTop <= 0 : currScrollTop >= bodyScrollElem.scrollHeight - bodyScrollElem.clientHeight) {
            return
          }
        }

        // 展开行处理，如果展开行嵌入表格中
        if ($xeParentTable) {
          if (isRollY) {
            if (checkTargetElement(evnt.target, [leftScrollElem, bodyScrollElem, rightScrollElem], evnt.currentTarget)) {
              evnt.stopPropagation()
              return
            }
          }
          if (isRollX) {
            if (checkTargetElement(evnt.target, [headerScrollElem, bodyScrollElem, footerScrollElem], evnt.currentTarget)) {
              evnt.stopPropagation()
              return
            }
          }
        }

        if (!(leftFixedWidth || rightFixedWidth || expandColumn)) {
          return
        }

        if (rowOpts.isHover || highlightHoverRow) {
          $xeTable.clearHoverRow()
        }

        // 用于鼠标纵向滚轮处理
        if (isRollX) {
          evnt.preventDefault()
          internalData.inWheelScroll = true
          wheelScrollLeftTo(scrollLeft, (offsetLeft: number) => {
            internalData.inWheelScroll = true
            const currLeftNum = offsetLeft
            setScrollLeft(xHandleEl, currLeftNum)
            setScrollLeft(bodyScrollElem, currLeftNum)
            setScrollLeft(headerScrollElem, currLeftNum)
            setScrollLeft(footerScrollElem, currLeftNum)
            if (scrollXLoad) {
              $xeTable.triggerScrollXEvent(evnt)
            }
            $xeTable.handleScrollEvent(evnt, isRollY, isRollX, bodyScrollElem.scrollTop, currLeftNum, {
              type: 'table',
              fixed: ''
            })
          })
        }
        if (isRollY) {
          evnt.preventDefault()
          internalData.inWheelScroll = true
          wheelScrollTopTo(scrollTop, (offsetTop: number) => {
            internalData.inWheelScroll = true
            const currTopNum = offsetTop
            setScrollTop(yHandleEl, currTopNum)
            setScrollTop(bodyScrollElem, currTopNum)
            setScrollTop(leftScrollElem, currTopNum)
            setScrollTop(rightScrollElem, currTopNum)
            setScrollTop(rowExpandEl, currTopNum)
            syncGanttScrollTop(currTopNum)
            if (scrollYLoad) {
              $xeTable.triggerScrollYEvent(evnt)
            }
            $xeTable.handleScrollEvent(evnt, isRollY, isRollX, offsetTop, bodyScrollElem.scrollLeft, {
              type: 'table',
              fixed: ''
            })
          })
        }
      },
      triggerVirtualScrollXEvent (evnt) {
        const { scrollXLoad } = reactData
        const { elemStore, inWheelScroll, lastScrollTop, inHeaderScroll, inBodyScroll, inFooterScroll } = internalData
        if (inHeaderScroll || inBodyScroll || inFooterScroll) {
          return
        }
        if (inWheelScroll) {
          return
        }
        const headerScrollElem = getRefElem(elemStore['main-header-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const footerScrollElem = getRefElem(elemStore['main-footer-scroll'])
        const yHandleEl = refScrollYHandleElem.value
        const wrapperEl = evnt.currentTarget as HTMLDivElement
        const { scrollLeft } = wrapperEl
        const yBodyEl = yHandleEl || bodyScrollElem
        let scrollTop = 0
        if (yBodyEl) {
          scrollTop = yBodyEl.scrollTop
        }
        const isRollX = true
        const isRollY = scrollTop !== lastScrollTop

        internalData.inVirtualScroll = true
        setScrollLeft(bodyScrollElem, scrollLeft)
        setScrollLeft(headerScrollElem, scrollLeft)
        setScrollLeft(footerScrollElem, scrollLeft)
        if (scrollXLoad) {
          $xeTable.triggerScrollXEvent(evnt)
        }
        $xeTable.handleScrollEvent(evnt, isRollY, isRollX, scrollTop, scrollLeft, {
          type: 'table',
          fixed: ''
        })
      },
      triggerVirtualScrollYEvent (evnt) {
        const { scrollYLoad } = reactData
        const { elemStore, inWheelScroll, lastScrollLeft, inHeaderScroll, inBodyScroll, inFooterScroll } = internalData
        if (inHeaderScroll || inBodyScroll || inFooterScroll) {
          return
        }
        if (inWheelScroll) {
          return
        }
        const leftScrollElem = getRefElem(elemStore['left-body-scroll'])
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const rightScrollElem = getRefElem(elemStore['right-body-scroll'])
        const rowExpandEl = refRowExpandElem.value
        const xHandleEl = refScrollXHandleElem.value
        const wrapperEl = evnt.currentTarget as HTMLDivElement
        const { scrollTop } = wrapperEl
        const xBodyEl = xHandleEl || bodyScrollElem
        let scrollLeft = 0
        if (xBodyEl) {
          scrollLeft = xBodyEl.scrollLeft
        }
        const isRollX = scrollLeft !== lastScrollLeft
        const isRollY = true
        internalData.inVirtualScroll = true
        setScrollTop(bodyScrollElem, scrollTop)
        setScrollTop(leftScrollElem, scrollTop)
        setScrollTop(rightScrollElem, scrollTop)
        setScrollTop(rowExpandEl, scrollTop)
        syncGanttScrollTop(scrollTop)
        if (scrollYLoad) {
          $xeTable.triggerScrollYEvent(evnt)
        }
        $xeTable.handleScrollEvent(evnt, isRollY, isRollX, scrollTop, scrollLeft, {
          type: 'table',
          fixed: ''
        })
      },
      /**
       * 对于树形结构中，可以直接滚动到指定深层节点中
       * 对于某些特定的场景可能会用到，比如定位到某一节点
       * @param {Row} row 行对象
       */
      scrollToTreeRow (row) {
        const { treeConfig } = props
        const { isRowGroupStatus } = reactData
        const { tableFullData } = internalData
        const rests: Promise<any>[] = []
        if (treeConfig || isRowGroupStatus) {
          const aggregateOpts = computeAggregateOpts.value
          const treeOpts = computeTreeOpts.value
          const childrenField = treeOpts.children || treeOpts.childrenField
          const matchObj = XEUtils.findTree(tableFullData, item => $xeTable.eqRow(item, row), { children: isRowGroupStatus ? aggregateOpts.mapChildrenField : childrenField })
          if (matchObj) {
            const nodes = matchObj.nodes
            nodes.forEach((row, index) => {
              if (index < nodes.length - 1 && !$xeTable.isTreeExpandByRow(row)) {
                rests.push($xeTable.setTreeExpand(row, true))
              }
            })
          }
        }
        return Promise.all(rests).then(() => rowToVisible($xeTable, row))
      },
      updateScrollYStatus,
      // 更新横向 X 可视渲染上下剩余空间大小
      updateScrollXSpace () {
        const { scrollXLoad, overflowX, scrollXWidth } = reactData
        const { visibleColumn, scrollXStore, elemStore, fullColumnIdData } = internalData
        const $xeGanttView = internalData.xeGanttView
        const mouseOpts = computeMouseOpts.value
        const tableBody = refTableBody.value
        const tableBodyElem = tableBody ? tableBody.$el as HTMLDivElement : null
        if (tableBodyElem) {
          const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
          const bodyTableElem = getRefElem(elemStore['main-body-table'])
          const headerTableElem = getRefElem(elemStore['main-header-table'])
          const footerTableElem = getRefElem(elemStore['main-footer-table'])

          let xSpaceLeft = 0
          const firstColumn = visibleColumn[scrollXStore.startIndex]
          if (firstColumn) {
            const colRest = fullColumnIdData[firstColumn.id] || {}
            xSpaceLeft = colRest.oLeft
          }

          let clientWidth = 0
          if (bodyScrollElem) {
            clientWidth = bodyScrollElem.clientWidth
          }
          // 虚拟渲染
          let isScrollXBig = false
          let ySpaceWidth = scrollXWidth
          if (scrollXWidth > maxXWidth) {
            // 触右
            if (bodyScrollElem && bodyTableElem && bodyScrollElem.scrollLeft + clientWidth >= maxXWidth) {
              xSpaceLeft = maxXWidth - bodyTableElem.clientWidth
            } else {
              xSpaceLeft = (maxXWidth - clientWidth) * (xSpaceLeft / (scrollXWidth - clientWidth))
            }
            ySpaceWidth = maxXWidth
            isScrollXBig = true
          }

          if (!(scrollXLoad && overflowX)) {
            xSpaceLeft = 0
          }

          if (getConfig().scrollMarginStyle) {
            // 已废弃方式
            if (headerTableElem) {
              headerTableElem.style.marginLeft = headerTableElem.getAttribute('xvm') ? `${xSpaceLeft}px` : ''
            }
            if (bodyTableElem) {
              bodyTableElem.style.marginLeft = `${xSpaceLeft}px`
            }
            if (footerTableElem) {
              footerTableElem.style.marginLeft = footerTableElem.getAttribute('xvm') ? `${xSpaceLeft}px` : ''
            }
          } else {
            if (headerTableElem) {
              headerTableElem.style.transform = headerTableElem.getAttribute('xvm') ? `translate(${xSpaceLeft}px, 0px)` : ''
            }
            if (bodyTableElem) {
              bodyTableElem.style.transform = `translate(${xSpaceLeft}px, ${reactData.scrollYTop || 0}px)`
            }
            if (footerTableElem) {
              footerTableElem.style.transform = footerTableElem.getAttribute('xvm') ? `translate(${xSpaceLeft}px, 0px)` : ''
            }
          }

          const containerList = ['main']
          containerList.forEach(name => {
            const layoutList = ['header', 'body', 'footer']
            layoutList.forEach(layout => {
              const xSpaceElem = getRefElem(elemStore[`${name}-${layout}-xSpace`])
              if (xSpaceElem) {
                xSpaceElem.style.width = scrollXLoad ? `${ySpaceWidth}px` : ''
              }
            })
          })

          reactData.scrollXLeft = xSpaceLeft
          reactData.scrollXWidth = ySpaceWidth
          reactData.isScrollXBig = isScrollXBig

          const scrollXSpaceEl = refScrollXSpaceElem.value
          if (scrollXSpaceEl) {
            scrollXSpaceEl.style.width = `${ySpaceWidth}px`
          }

          calcScrollbar()
          if (isScrollXBig && mouseOpts.area) {
            errLog('vxe.error.notProp', ['mouse-config.area'])
          }
          if ($xeGanttView && $xeGanttView.handleUpdateSXSpace) {
            $xeGanttView.handleUpdateSXSpace()
          }
          return nextTick().then(() => {
            updateStyle()
          })
        }
      },
      // 更新纵向 Y 可视渲染上下剩余空间大小
      updateScrollYSpace () {
        const { isAllOverflow, overflowY, scrollYLoad, scrollYHeight, expandColumn } = reactData
        const { scrollYStore, elemStore, isResizeCellHeight, afterFullData, fullAllDataRowIdData, rowExpandedMaps } = internalData
        const $xeGanttView = internalData.xeGanttView
        const { startIndex } = scrollYStore
        const mouseOpts = computeMouseOpts.value
        const expandOpts = computeExpandOpts.value
        const rowOpts = computeRowOpts.value
        const cellOpts = computeCellOpts.value
        const defaultRowHeight = computeDefaultRowHeight.value
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const bodyTableElem = getRefElem(elemStore['main-body-table'])
        const leftBodyTableElem = getRefElem(elemStore['left-body-table'])
        const rightbodyTableElem = getRefElem(elemStore['right-body-table'])
        const containerList = ['main', 'left', 'right']
        let ySpaceTop = 0
        let sYHeight = scrollYHeight
        let isScrollYBig = false
        if (scrollYLoad) {
          const isCustomCellHeight = isResizeCellHeight || cellOpts.height || rowOpts.height
          if (!isCustomCellHeight && !expandColumn && isAllOverflow) {
            sYHeight = afterFullData.length * defaultRowHeight
            if (sYHeight > maxYHeight) {
              isScrollYBig = true
            }
            ySpaceTop = Math.max(0, startIndex * defaultRowHeight)
          } else {
            const firstRow = afterFullData[startIndex]
            if (firstRow) {
              let rowid = getRowid($xeTable, firstRow)
              let rowRest = fullAllDataRowIdData[rowid] || {}
              ySpaceTop = (rowRest.oTop || 0)

              const lastRow = afterFullData[afterFullData.length - 1]
              rowid = getRowid($xeTable, lastRow)
              rowRest = fullAllDataRowIdData[rowid] || {}
              if (rowRest) {
                const rHeight = getCellRestHeight(rowRest, cellOpts, rowOpts, defaultRowHeight)
                // 如果为空时还没计算完数据，保持原高度不变
                if (rHeight) {
                  sYHeight = (rowRest.oTop || 0) + rHeight
                  // 是否展开行
                  if (expandColumn && rowExpandedMaps[rowid]) {
                    sYHeight += rowRest.expandHeight || expandOpts.height || 0
                  }
                }
              }
              if (sYHeight > maxYHeight) {
                isScrollYBig = true
              }
            } else {
              sYHeight = bodyTableElem ? bodyTableElem.clientHeight : 0
            }
          }
        } else {
          if (bodyTableElem) {
            sYHeight = bodyTableElem.clientHeight
          }
        }
        let clientHeight = 0
        if (bodyScrollElem) {
          clientHeight = bodyScrollElem.clientHeight
        }
        // 虚拟渲染
        let ySpaceHeight = sYHeight
        let scrollYTop = ySpaceTop
        if (isScrollYBig) {
          // 触底
          if (bodyScrollElem && bodyTableElem && bodyScrollElem.scrollTop + clientHeight >= maxYHeight) {
            scrollYTop = maxYHeight - bodyTableElem.clientHeight
          } else {
            scrollYTop = (maxYHeight - clientHeight) * (ySpaceTop / (scrollYHeight - clientHeight))
          }
          ySpaceHeight = maxYHeight
        }
        if (!(scrollYLoad && overflowY)) {
          scrollYTop = 0
        }

        if (getConfig().scrollMarginStyle) {
          if (leftBodyTableElem) {
            leftBodyTableElem.style.marginTop = `${scrollYTop}px`
          }
          if (bodyTableElem) {
            bodyTableElem.style.marginTop = `${scrollYTop}px`
          }
          if (rightbodyTableElem) {
            rightbodyTableElem.style.marginTop = `${scrollYTop}px`
          }
        } else {
          if (leftBodyTableElem) {
            leftBodyTableElem.style.transform = `translate(0px, ${scrollYTop}px)`
          }
          if (bodyTableElem) {
            bodyTableElem.style.transform = `translate(${reactData.scrollXLeft || 0}px, ${scrollYTop}px)`
          }
          if (rightbodyTableElem) {
            rightbodyTableElem.style.transform = `translate(0px, ${scrollYTop}px)`
          }
        }

        containerList.forEach(name => {
          const layoutList = ['header', 'body', 'footer']
          layoutList.forEach(layout => {
            const ySpaceElem = getRefElem(elemStore[`${name}-${layout}-ySpace`])
            if (ySpaceElem) {
              ySpaceElem.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
            }
          })
        })

        const scrollYSpaceEl = refScrollYSpaceElem.value
        if (scrollYSpaceEl) {
          scrollYSpaceEl.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
        }
        const rowExpandYSpaceEl = refRowExpandYSpaceElem.value
        if (rowExpandYSpaceEl) {
          rowExpandYSpaceEl.style.height = ySpaceHeight ? `${ySpaceHeight}px` : ''
        }
        reactData.scrollYTop = scrollYTop
        reactData.scrollYHeight = isScrollYBig ? sYHeight : ySpaceHeight
        reactData.isScrollYBig = isScrollYBig

        calcScrollbar()
        if (isScrollYBig && mouseOpts.area) {
          errLog('vxe.error.notProp', ['mouse-config.area'])
        }
        if ($xeGanttView && $xeGanttView.handleUpdateSYSpace) {
          $xeGanttView.handleUpdateSYSpace()
        }
        return nextTick().then(() => {
          return updateStyle()
        })
      },
      updateScrollXData () {
        const { isAllOverflow } = reactData
        handleTableColumn()
        $xeTable.updateScrollXSpace()
        return nextTick().then(() => {
          handleTableColumn()
          $xeTable.updateScrollXSpace()
          if (!isAllOverflow) {
            $xeTable.updateScrollYSpace()
          }
        })
      },
      updateScrollYData () {
        $xeTable.handleTableData()
        $xeTable.updateScrollYSpace()
        return nextTick().then(() => {
          $xeTable.handleTableData()
          $xeTable.updateScrollYSpace()
        })
      },
      /**
       * 处理固定列的显示状态
       */
      checkScrolling () {
        const { elemStore } = internalData
        const bodyScrollElem = getRefElem(elemStore['main-body-scroll'])
        const leftContainerElem = refLeftContainer.value
        const rightContainerElem = refRightContainer.value
        const xHandleEl = refScrollXHandleElem.value
        const bodtTargetEl = xHandleEl || bodyScrollElem
        if (bodtTargetEl) {
          if (leftContainerElem) {
            if (bodtTargetEl.scrollLeft > 0) {
              addClass(leftContainerElem, 'scrolling--middle')
            } else {
              removeClass(leftContainerElem, 'scrolling--middle')
            }
          }
          if (rightContainerElem) {
            if (bodtTargetEl.clientWidth < bodtTargetEl.scrollWidth - Math.ceil(bodtTargetEl.scrollLeft)) {
              addClass(rightContainerElem, 'scrolling--middle')
            } else {
              removeClass(rightContainerElem, 'scrolling--middle')
            }
          }
        }
      },
      handleUpdateAggData () {
        return loadTableData(internalData.tableSynchData, false, true)
      },
      updateZindex () {
        if (props.zIndex) {
          internalData.tZindex = props.zIndex
        } else if (internalData.tZindex < getLastZIndex()) {
          internalData.tZindex = nextZIndex()
        }
      },
      handleCheckedCheckboxRow,
      /**
       * 行 hover 事件
       */
      triggerHoverEvent (evnt, { row }) {
        $xeTable.setHoverRow(row)
      },
      setHoverRow (row) {
        const $xeGanttView = internalData.xeGanttView
        const rowid = getRowid($xeTable, row)
        const el = refElem.value
        $xeTable.clearHoverRow()
        if (el) {
          XEUtils.arrayEach(el.querySelectorAll(`.vxe-body--row[rowid="${rowid}"]`), elem => addClass(elem, 'row--hover'))
        }
        internalData.hoverRow = row
        if ($xeGanttView && $xeGanttView.handleUpdateHoverRow) {
          $xeGanttView.handleUpdateHoverRow(row)
        }
      },
      clearHoverRow () {
        const $xeGanttView = internalData.xeGanttView
        const el = refElem.value
        if (el) {
          XEUtils.arrayEach(el.querySelectorAll('.vxe-body--row.row--hover'), elem => removeClass(elem, 'row--hover'))
        }
        internalData.hoverRow = null
        if ($xeGanttView && $xeGanttView.handleUpdateHoverRow) {
          $xeGanttView.handleUpdateHoverRow()
        }
      },
      /**
       * 已废弃，被 getCellElement 替换
       * @deprecated
       */
      getCell (row, column) {
        return $xeTable.getCellElement(row, column)
      },
      findRowIndexOf (list, row) {
        return row ? XEUtils.findIndexOf(list, item => $xeTable.eqRow(item, row)) : -1
      },
      eqRow (row1, row2) {
        if (row1 && row2) {
          if (row1 === row2) {
            return true
          }
          return getRowid($xeTable, row1) === getRowid($xeTable, row2)
        }
        return false
      },
      handleConnectGanttView ($ganttView) {
        if ($ganttView && $ganttView.connectUpdate) {
          $ganttView.connectUpdate({ $table: $xeTable })
          internalData.xeGanttView = $ganttView as any
        }
        return nextTick()
      }
    }

    // 检测对应模块是否安装
    // 'openExport,openPrint,exportData,openImport,importData,saveFile,readFile,importByFile,print'.split(',').forEach(name => {
    //   ($xeTable as any)[name] = function () {
    //     errLog('vxe.error.reqModule', ['Export'])
    //   }
    // })
    // 'clearValidate,fullValidate,validate'.split(',').forEach(name => {
    //   ($xeTable as any)[name] = function () {
    //     errLog('vxe.error.reqModule', ['Validator'])
    //   }
    // })

    Object.assign($xeTable, tableMethods, tablePrivateMethods)

    /**
     * 渲染浮固定列
     * 分别渲染左边固定列和右边固定列
     * 如果宽度足够情况下，则不需要渲染固定列
     * @param {String} fixedType 固定列类型
     */
    const renderViewFixed = (fixedType: 'left' | 'right') => {
      const { showHeader, showFooter } = props
      const { tableData, tableColumn, tableGroupColumn, columnStore, footerTableData } = reactData
      const scrollbarOpts = computeScrollbarOpts.value
      const scrollbarXOpts = computeScrollbarXOpts.value
      const scrollbarYOpts = computeScrollbarYOpts.value
      const { overscrollBehavior: overscrollXBehavior } = scrollbarXOpts
      const { overscrollBehavior: overscrollYBehavior } = scrollbarYOpts
      const isFixedLeft = fixedType === 'left'
      const fixedColumn = isFixedLeft ? columnStore.leftList : columnStore.rightList
      const osXBehavior = XEUtils.eqNull(overscrollXBehavior) ? scrollbarOpts.overscrollBehavior : overscrollXBehavior
      const osYBehavior = XEUtils.eqNull(overscrollYBehavior) ? scrollbarOpts.overscrollBehavior : overscrollYBehavior
      return h('div', {
        ref: isFixedLeft ? refLeftContainer : refRightContainer,
        class: [`vxe-table--fixed-${fixedType}-wrapper`, `sx--${scrollbarXOpts.visible}`, `sy--${scrollbarYOpts.visible}`, {
          [`x-ob--${osXBehavior}`]: osXBehavior,
          [`y-ob--${osYBehavior}`]: osYBehavior
        }]
      }, [
        showHeader
          ? h(TableHeaderComponent, {
            ref: isFixedLeft ? refTableLeftHeader : refTableRightHeader,
            fixedType,
            tableData,
            tableColumn,
            tableGroupColumn,
            fixedColumn
          })
          : renderEmptyElement($xeTable),
        h(TableBodyComponent, {
          ref: isFixedLeft ? refTableLeftBody : refTableRightBody,
          fixedType,
          tableData,
          tableColumn,
          fixedColumn
        }),
        showFooter
          ? h(TableFooterComponent, {
            ref: isFixedLeft ? refTableLeftFooter : refTableRightFooter,
            footerTableData,
            tableColumn,
            fixedColumn,
            fixedType
          })
          : renderEmptyElement($xeTable)
      ])
    }

    const renderEmptyBody = () => {
      const emptyOpts = computeEmptyOpts.value
      const emptySlot = slots.empty
      const emptyParams = { $table: $xeTable, $grid: $xeGrid, gantt: $xeGantt }
      if (emptySlot) {
        return emptySlot(emptyParams)
      } else {
        const compConf = emptyOpts.name ? renderer.get(emptyOpts.name) : null
        const rtEmptyView = compConf ? (compConf.renderTableEmpty || compConf.renderTableEmptyView || compConf.renderEmpty) : null
        if (rtEmptyView) {
          return getSlotVNs(rtEmptyView(emptyOpts, emptyParams))
        }
      }
      return getFuncText(props.emptyText) || getI18n('vxe.table.emptyText')
    }

    const renderDragTipContents = () => {
      const { dragConfig } = props
      const { dragRow, dragCol, dragTipText } = reactData
      const columnDragOpts = computeColumnDragOpts.value
      const rowDragOpts = computeRowDragOpts.value
      const rowDragSlots = rowDragOpts.slots || {}
      const rTipSlot = rowDragSlots.tip || (dragConfig && dragConfig.slots ? dragConfig.slots.rowTip : null)
      const columnDragSlots = columnDragOpts.slots || {}
      const cTipSlot = columnDragSlots.tip
      const dRow = dragRow || (rowDragOpts.isCrossTableDrag ? crossTableDragRowInfo.row : null)

      if (dRow && rTipSlot) {
        return callSlot(rTipSlot, { row: dRow })
      }
      if (dragCol && cTipSlot) {
        return callSlot(cTipSlot, { column: dragCol })
      }
      return [h('span', dragTipText)]
    }

    const renderDragTip = () => {
      const { dragRow, dragCol } = reactData
      const rowOpts = computeRowOpts.value
      const columnOpts = computeColumnOpts.value
      const rowDragOpts = computeRowDragOpts.value
      const columnDragOpts = computeColumnDragOpts.value
      const dRow = dragRow || (rowDragOpts.isCrossTableDrag ? crossTableDragRowInfo.row : null)

      if (rowOpts.drag || columnOpts.drag) {
        return h('div', {
          class: 'vxe-table--drag-wrapper'
        }, [
          h('div', {
            ref: refDragRowLineElem,
            class: ['vxe-table--drag-row-line', {
              'is--guides': rowDragOpts.showGuidesStatus
            }]
          }),
          h('div', {
            ref: refDragColLineElem,
            class: ['vxe-table--drag-col-line', {
              'is--guides': columnDragOpts.showGuidesStatus
            }]
          }),
          (dRow && rowDragOpts.showDragTip) || (dragCol && columnDragOpts.showDragTip)
            ? h('div', {
              ref: refDragTipElem,
              class: 'vxe-table--drag-sort-tip'
            }, [
              h('div', {
                class: 'vxe-table--drag-sort-tip-wrapper'
              }, [
                h('div', {
                  class: 'vxe-table--drag-sort-tip-status'
                }, [
                  h('span', {
                    class: ['vxe-table--drag-sort-tip-normal-status', dRow ? getIcon().TABLE_DRAG_STATUS_ROW : getIcon().TABLE_DRAG_STATUS_COLUMN]
                  }),
                  h('span', {
                    class: ['vxe-table--drag-sort-tip-sub-status', getIcon().TABLE_DRAG_STATUS_SUB_ROW]
                  }),
                  h('span', {
                    class: ['vxe-table--drag-sort-tip-disabled-status', getIcon().TABLE_DRAG_DISABLED]
                  })
                ]),
                h('div', {
                  class: 'vxe-table--drag-sort-tip-content'
                }, renderDragTipContents())
              ])
            ])
            : renderEmptyElement($xeTable)
        ])
      }
      return renderEmptyElement($xeTable)
    }

    const renderRowExpandedVNs = () => {
      const { treeConfig } = props
      const { expandColumn, isRowGroupStatus } = reactData
      const tableRowExpandedList = computeTableRowExpandedList.value
      const expandOpts = computeExpandOpts.value
      const { mode } = expandOpts
      if (mode !== 'fixed') {
        return renderEmptyElement($xeTable)
      }
      const expandVNs = [
        h('div', {
          key: 'repY',
          ref: refRowExpandYSpaceElem
        })
      ]
      if (expandColumn) {
        const { handleGetRowId } = createHandleGetRowId($xeTable)
        tableRowExpandedList.forEach((row) => {
          const expandOpts = computeExpandOpts.value
          const { height: expandHeight, padding, indent } = expandOpts
          const { fullAllDataRowIdData, fullColumnIdData } = internalData
          const treeOpts = computeTreeOpts.value
          const { transform, seqMode } = treeOpts
          const cellStyle: Record<string, string> = {}
          const rowid = handleGetRowId(row)
          const rowRest = fullAllDataRowIdData[rowid]
          const colid = expandColumn.id
          const colRest = fullColumnIdData[colid] || {}
          let rowLevel = 0
          let seq: string | number = -1
          let _rowIndex = -1
          let rowIndex = -1
          let $rowIndex = -1
          if (rowRest) {
            rowIndex = rowRest.index
            $rowIndex = rowRest.$index
            _rowIndex = rowRest._index
            rowLevel = rowRest.level
            seq = rowRest.seq
            if (isRowGroupStatus || (treeConfig && transform && seqMode === 'increasing')) {
              seq = rowRest._index + 1
            } else if ((treeConfig && seqMode === 'fixed')) {
              seq = rowRest._tIndex + 1
            }
          }
          if (expandHeight) {
            cellStyle.height = `${expandHeight}px`
          }
          if (isRowGroupStatus || treeConfig) {
            cellStyle.paddingLeft = `${(rowLevel * (XEUtils.isNumber(indent) ? indent : treeOpts.indent)) + 30}px`
          }
          let columnIndex = -1
          let $columnIndex = -1
          let _columnIndex = -1
          if (colRest) {
            columnIndex = colRest.index
            $columnIndex = colRest.$index
            _columnIndex = colRest._index
          }
          const expandParams: VxeTableDefines.CellRenderDataParams = {
            $table: $xeTable,
            $grid: $xeGrid,
            $gantt: $xeGantt,
            seq,
            column: expandColumn,
            columnIndex,
            $columnIndex,
            _columnIndex,
            fixed: '',
            source: 'table',
            type: 'body',
            level: rowLevel,
            rowid,
            row,
            rowIndex,
            $rowIndex,
            _rowIndex,
            isHidden: false,
            isEdit: false,
            visibleData: [],
            data: [],
            items: []
          }
          expandVNs.push(
            h('div', {
              key: rowid,
              class: ['vxe-body--row-expanded-cell', {
                'is--padding': padding,
                'is--ellipsis': expandHeight
              }],
              rowid,
              style: cellStyle
            }, [
              h('div', {
                class: 'vxe-body--row-expanded-content'
              }, expandColumn.renderData(expandParams))
            ])
          )
        })
      }

      return h('div', {
        ref: refRowExpandElem,
        class: 'vxe-table--row-expanded-wrapper'
      }, expandVNs)
    }

    const renderScrollX = () => {
      return h('div', {
        key: 'vsx',
        ref: refScrollXVirtualElem,
        class: 'vxe-table--scroll-x-virtual'
      }, [
        h('div', {
          ref: refScrollXLeftCornerElem,
          class: 'vxe-table--scroll-x-left-corner'
        }),
        h('div', {
          ref: refScrollXWrapperElem,
          class: 'vxe-table--scroll-x-wrapper'
        }, [
          h('div', {
            ref: refScrollXHandleElem,
            class: 'vxe-table--scroll-x-handle',
            onScroll: $xeTable.triggerVirtualScrollXEvent
          }, [
            h('div', {
              ref: refScrollXSpaceElem,
              class: 'vxe-table--scroll-x-space'
            })
          ]),
          h('div', {
            class: 'vxe-table--scroll-x-handle-appearance'
          })
        ]),
        h('div', {
          ref: refScrollXRightCornerElem,
          class: 'vxe-table--scroll-x-right-corner'
        })
      ])
    }

    const renderScrollY = () => {
      return h('div', {
        ref: refScrollYVirtualElem,
        class: 'vxe-table--scroll-y-virtual'
      }, [
        h('div', {
          ref: refScrollYTopCornerElem,
          class: 'vxe-table--scroll-y-top-corner'
        }),
        h('div', {
          ref: refScrollYWrapperElem,
          class: 'vxe-table--scroll-y-wrapper'
        }, [
          h('div', {
            ref: refScrollYHandleElem,
            class: 'vxe-table--scroll-y-handle',
            onScroll: $xeTable.triggerVirtualScrollYEvent
          }, [
            h('div', {
              ref: refScrollYSpaceElem,
              class: 'vxe-table--scroll-y-space'
            })
          ]),
          h('div', {
            class: 'vxe-table--scroll-y-handle-appearance'
          })
        ]),
        h('div', {
          ref: refScrollYBottomCornerElem,
          class: 'vxe-table--scroll-y-bottom-corner'
        })
      ])
    }

    const renderViewport = () => {
      const { showHeader, showFooter } = props
      const { overflowX, tableData, tableColumn, tableGroupColumn, footerTableData, columnStore } = reactData
      const scrollbarOpts = computeScrollbarOpts.value
      const scrollbarXOpts = computeScrollbarXOpts.value
      const scrollbarYOpts = computeScrollbarYOpts.value
      const { overscrollBehavior: overscrollXBehavior } = scrollbarXOpts
      const { overscrollBehavior: overscrollYBehavior } = scrollbarYOpts
      const { leftList, rightList } = columnStore
      const osXBehavior = XEUtils.eqNull(overscrollXBehavior) ? scrollbarOpts.overscrollBehavior : overscrollXBehavior
      const osYBehavior = XEUtils.eqNull(overscrollYBehavior) ? scrollbarOpts.overscrollBehavior : overscrollYBehavior

      return h('div', {
        ref: refTableViewportElem,
        class: ['vxe-table--viewport-wrapper', {
          [`x-ob--${osXBehavior}`]: osXBehavior,
          [`y-ob--${osYBehavior}`]: osYBehavior
        }]
      }, [
        h('div', {
          class: ['vxe-table--main-wrapper', `sx--${scrollbarXOpts.visible}`, `sy--${scrollbarYOpts.visible}`]
        }, [
          /**
           * 表头
           */
          showHeader
            ? h(TableHeaderComponent, {
              ref: refTableHeader,
              tableData,
              tableColumn,
              tableGroupColumn
            })
            : renderEmptyElement($xeTable),
          /**
           * 表体
           */
          h(TableBodyComponent, {
            ref: refTableBody,
            tableData,
            tableColumn
          }),
          /**
           * 表尾
           */
          showFooter
            ? h(TableFooterComponent, {
              ref: refTableFooter,
              footerTableData,
              tableColumn
            })
            : renderEmptyElement($xeTable)
        ]),
        h('div', {
          class: 'vxe-table--fixed-wrapper'
        }, [
          leftList && leftList.length && overflowX ? renderViewFixed('left') : renderEmptyElement($xeTable),
          rightList && rightList.length && overflowX ? renderViewFixed('right') : renderEmptyElement($xeTable)
        ]),
        renderRowExpandedVNs()
      ])
    }

    const renderBody = () => {
      const scrollbarYToLeft = computeScrollbarYToLeft.value
      return h('div', {
        class: 'vxe-table--layout-wrapper'
      }, scrollbarYToLeft
        ? [
            renderScrollY(),
            renderViewport()
          ]
        : [
            renderViewport(),
            renderScrollY()
          ])
    }

    const renderVN = () => {
      const { loading, stripe, showHeader, height, treeConfig, mouseConfig, showFooter, highlightCell, highlightHoverRow, highlightHoverColumn, editConfig, editRules } = props
      const { isGroup, overflowX, overflowY, scrollXLoad, scrollYLoad, tableData, initStore, isRowGroupStatus, columnStore, filterStore, customStore, tooltipStore } = reactData
      const { teleportToWrapperElem, popupToWrapperElem, customPopupToElem } = internalData
      const { leftList, rightList } = columnStore
      const loadingSlot = slots.loading
      const tipSlots = {
        header: slots.headerTooltip || slots['header-tooltip'],
        body: slots.tooltip,
        footer: slots.footerTooltip || slots['footer-tooltip']
      }
      const currTooltipSlot = tooltipStore.visible && tooltipStore.type ? tipSlots[tooltipStore.type] : null
      const tableStyle = computeTableStyle.value
      const rowDragOpts = computeRowDragOpts.value
      const tableTipConfig = computeTableTipConfig.value
      const validTipConfig = computeValidTipConfig.value
      const validOpts = computeValidOpts.value
      const checkboxOpts = computeCheckboxOpts.value
      const treeOpts = computeTreeOpts.value
      const rowOpts = computeRowOpts.value
      const columnOpts = computeColumnOpts.value
      const vSize = computeSize.value
      const tableBorder = computeTableBorder.value
      const mouseOpts = computeMouseOpts.value
      const areaOpts = computeAreaOpts.value
      const loadingOpts = computeLoadingOpts.value
      const isContentMenu = computeIsContentMenu.value
      const currLoading = reactData.isColLoading || reactData.isRowLoading || loading
      const resizableOpts = computeResizableOpts.value
      const isArea = mouseConfig && mouseOpts.area
      const columnDragOpts = computeColumnDragOpts.value
      const scrollbarXToTop = computeScrollbarXToTop.value
      const scrollbarYToLeft = computeScrollbarYToLeft.value
      const { isCrossTableDrag } = rowDragOpts
      const tbOns: {
        onContextmenu: (...args: any[]) => void
        onKeydown: (...args: any[]) => void
        onDragover?: (...args: any[]) => void
      } = {
        onKeydown: keydownEvent,
        onContextmenu: contextMenuEvent
      }
      if (isCrossTableDrag && !tableData.length) {
        tbOns.onDragover = $xeTable.handleCrossTableRowDragoverEmptyEvent
      }
      return h('div', {
        ref: refElem,
        class: ['vxe-table', 'vxe-table--render-default', `tid_${xID}`, `border--${tableBorder}`, `sx-pos--${scrollbarXToTop ? 'top' : 'bottom'}`, `sy-pos--${scrollbarYToLeft ? 'left' : 'right'}`, {
          [`size--${vSize}`]: vSize,
          [`valid-msg--${validOpts.msgMode}`]: !!editRules,
          'vxe-editable': !!editConfig,
          'old-cell-valid': editRules && getConfig().cellVaildMode === 'obsolete',
          'cell--highlight': highlightCell,
          'cell--selected': mouseConfig && mouseOpts.selected,
          'cell--area': isArea,
          'header-cell--area': isArea && areaOpts.selectCellByHeader,
          'body-cell--area': isArea && areaOpts.selectCellByBody,
          'row--highlight': rowOpts.isHover || highlightHoverRow,
          'column--highlight': columnOpts.isHover || highlightHoverColumn,
          'checkbox--range': checkboxOpts.range,
          'col--drag-cell': columnOpts.drag && columnDragOpts.trigger === 'cell',
          'is--header': showHeader,
          'not--header': !showHeader,
          'is--footer': showFooter,
          'not--footer': !showFooter,
          'is--group': isGroup,
          'is-row-group': isRowGroupStatus,
          'is--tree-line': treeConfig && (treeOpts.showLine || treeOpts.line),
          'is--fixed-left': leftList.length,
          'is--fixed-right': rightList.length,
          'is--animat': !!props.animat,
          'is--round': props.round,
          'is--stripe': !treeConfig && stripe,
          'is--loading': currLoading,
          'is--empty': !currLoading && !tableData.length,
          'is--scroll-y': overflowY,
          'not--scroll-y': !overflowY,
          'is--scroll-x': overflowX,
          'not--scroll-x': !overflowX,
          'is--virtual-x': scrollXLoad,
          'is--virtual-y': scrollYLoad
        }],
        style: tableStyle,
        spellcheck: false,
        ...tbOns
      }, [
        /**
         * 隐藏列
         */
        h('div', {
          class: 'vxe-table-slots'
        }, slots.default ? slots.default({}) : []),
        h('div', {
          ref: refVarElem,
          class: 'vxe-table-vars'
        }, [
          h('div', {
            class: 'vxe-table-var-default'
          }),
          h('div', {
            class: 'vxe-table-var-medium'
          }),
          h('div', {
            class: 'vxe-table-var-small'
          }),
          h('div', {
            class: 'vxe-table-var-mini'
          })
        ]),
        h('div', {
          key: 'tw',
          class: 'vxe-table--render-wrapper'
        }, scrollbarXToTop
          ? [
              renderScrollX(),
              renderBody()
            ]
          : [
              renderBody(),
              renderScrollX()
            ]),
        /**
         * 空数据
         */
        h('div', {
          key: 'tn',
          ref: refEmptyPlaceholder,
          class: 'vxe-table--empty-place-wrapper',
          xid: xID
        }, [
          h('div', {
            class: 'vxe-table--empty-placeholder'
          }, [
            h('div', {
              class: 'vxe-table--empty-content'
            }, renderEmptyBody())
          ])
        ]),
        /**
         * 边框线
         */
        h('div', {
          key: 'tl',
          class: 'vxe-table--border-line'
        }),
        /**
         * 列宽线
         */
        h('div', {
          key: 'tcl',
          ref: refColResizeBar,
          class: 'vxe-table--resizable-col-bar'
        }, resizableOpts.showDragTip
          ? [
              h('div', {
                class: 'vxe-table--resizable-number-tip'
              })
            ]
          : []),
        h('div', {
          key: 'ttw'
        }, [
          h(Teleport, {
            to: teleportToWrapperElem,
            disabled: !($xeGantt && teleportToWrapperElem)
          }, [
            h('div', {
              ref: refTeleportWrapper
            }, [
              /**
               * 行高线
               */
              h('div', {
                key: 'trl',
                ref: refRowResizeBar,
                class: 'vxe-table--resizable-row-bar'
              }, resizableOpts.showDragTip
                ? [
                    h('div', {
                      class: 'vxe-table--resizable-number-tip'
                    })
                  ]
                : []),
              /**
               * 加载中
               */
              VxeUILoadingComponent
                ? h(VxeUILoadingComponent, {
                  key: 'lg',
                  class: 'vxe-table--loading',
                  modelValue: currLoading,
                  icon: loadingOpts.icon,
                  text: loadingOpts.text
                }, loadingSlot
                  ? {
                      default: () => callSlot(loadingSlot, { $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, loading: currLoading })
                    }
                  : {})
                : loadingSlot
                  ? h('div', {
                    class: ['vxe-loading--custom-wrapper', {
                      'is--visible': currLoading
                    }]
                  }, callSlot(loadingSlot, { $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, loading: currLoading }))
                  : renderEmptyElement($xeTable),
              /**
               * 拖拽排序提示
               */
              renderDragTip()
            ])
          ])
        ]),
        h('div', {
          key: 'fpw',
          ref: refCustomContainerElem
        }, [
          h(Teleport, {
            to: customPopupToElem,
            disabled: !!reactData.ctPopupFlag && !customPopupToElem
          }, [
            /**
             * 自定义列
             */
            initStore.custom
              ? h(TableCustomPanelComponent, {
                key: 'cs',
                ref: refTableCustom,
                customStore
              })
              : renderEmptyElement($xeTable)
          ])
        ]),
        h('div', {
          key: 'tpw'
        }, [
          h(Teleport, {
            to: popupToWrapperElem,
            disabled: !($xeGGWrapper && popupToWrapperElem)
          }, [
            h('div', {
              ref: refPopupWrapperElem
            }, [
              /**
               * 筛选
               */
              initStore.filter
                ? h(TableFilterPanelComponent, {
                  key: 'tf',
                  ref: refTableFilter,
                  filterStore
                })
                : renderEmptyElement($xeTable),
              /**
               * 快捷菜单
               */
              isContentMenu
                ? h(TableMenuPanelComponent, {
                  key: 'tm',
                  ref: refTableMenu
                })
                : renderEmptyElement($xeTable)
            ])
          ])
        ]),
        /**
         * 导入
         */
        initStore.import && props.importConfig
          ? h(TableImportPanelComponent, {
            key: 'it',
            defaultOptions: reactData.importParams,
            storeData: reactData.importStore
          })
          : renderEmptyElement($xeTable),
        /**
         * 导出
         */
        initStore.export && (props.exportConfig || props.printConfig)
          ? h(TableExportPanelComponent, {
            key: 'et',
            defaultOptions: reactData.exportParams,
            storeData: reactData.exportStore
          })
          : renderEmptyElement($xeTable),
        /**
         * 提示相关
         */
        VxeUITooltipComponent
          ? h('div', {}, [
            /**
             * 通用提示
             */
            h(VxeUITooltipComponent, {
              key: 'ctp',
              ref: refCommTooltip,
              isArrow: false,
              enterable: false
            }),
            /**
              * 工具提示
              */
            h(VxeUITooltipComponent, {
              key: 'btp',
              ref: refTooltip,
              theme: tableTipConfig.theme,
              enterable: tableTipConfig.enterable,
              enterDelay: tableTipConfig.enterDelay,
              leaveDelay: tableTipConfig.leaveDelay,
              useHTML: tableTipConfig.useHTML,
              width: tableTipConfig.width,
              height: tableTipConfig.height,
              minWidth: tableTipConfig.minWidth,
              minHeight: tableTipConfig.minHeight,
              maxWidth: tableTipConfig.maxWidth,
              maxHeight: tableTipConfig.maxHeight,
              placement: tableTipConfig.placement,
              defaultPlacement: tableTipConfig.defaultPlacement,
              popupClassName: tableTipConfig.popupClassName
            }, currTooltipSlot
              ? {
                  content: () => {
                    const { type, row, column, content: tooltipContent } = tooltipStore
                    if (currTooltipSlot) {
                      if (column && type === 'header') {
                        return h('div', {
                          key: type
                        }, currTooltipSlot({ column, tooltipContent, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }))
                      }
                      if (row && column && type === 'body') {
                        return h('div', {
                          key: type
                        }, currTooltipSlot({ row, column, tooltipContent, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }))
                      }
                      if (row && column && type === 'footer') {
                        return h('div', {
                          key: type
                        }, currTooltipSlot({ row, column, tooltipContent, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }))
                      }
                    }
                    return renderEmptyElement($xeTable)
                  }
                }
              : {}),
            /**
              * 校验提示
              */
            props.editRules && validOpts.showErrorMessage && (validOpts.message === 'default' ? !height : validOpts.message === 'tooltip')
              ? h(VxeUITooltipComponent, {
                key: 'vtp',
                ref: refValidTooltip,
                class: [{
                  'old-cell-valid': editRules && getConfig().cellVaildMode === 'obsolete'
                }, 'vxe-table--valid-error'],
                theme: validTipConfig.theme,
                enterable: validTipConfig.enterable,
                enterDelay: validTipConfig.enterDelay,
                leaveDelay: validTipConfig.leaveDelay
              })
              : renderEmptyElement($xeTable)
          ])
          : renderEmptyElement($xeTable)
      ])
    }

    const dataFlag = ref(0)
    watch(() => props.data ? props.data.length : -1, () => {
      dataFlag.value++
    })
    watch(() => props.data, () => {
      dataFlag.value++
    })
    watch(dataFlag, () => {
      const { initStatus } = internalData
      const value = props.data || []
      if (value && value.length >= 20000) {
        warnLog('vxe.error.errLargeData', ['loadData(data), reloadData(data)'])
      }
      loadTableData(value, false, true).then(() => {
        const { scrollXLoad, scrollYLoad, expandColumn } = reactData
        const expandOpts = computeExpandOpts.value
        internalData.inited = true
        internalData.initStatus = true
        if (!initStatus) {
          handleLoadDefaults()
        }
        // const checkboxOpts = computeCheckboxOpts.value
        // const checkboxColumn = internalData.tableFullColumn.find(column => column.type === 'checkbox')
        // if (checkboxColumn && internalData.tableFullData.length > 300 && !checkboxOpts.checkField) {
        //   warnLog('vxe.error.checkProp', ['checkbox-config.checkField'])
        // }
        if ((scrollXLoad || scrollYLoad) && (expandColumn && expandOpts.mode !== 'fixed')) {
          warnLog('vxe.error.scrollErrProp', ['column.type=expand'])
        }
        return $xeTable.recalculate()
      })
    })

    const staticColumnFlag = ref(0)
    watch(() => reactData.staticColumns.length, () => {
      staticColumnFlag.value++
    })
    watch(() => reactData.staticColumns, () => {
      staticColumnFlag.value++
    })
    watch(staticColumnFlag, () => {
      nextTick(() => handleInitColumn(XEUtils.clone(reactData.staticColumns)))
    })

    const tableColumnFlag = ref(0)
    watch(() => reactData.tableColumn.length, () => {
      tableColumnFlag.value++
    })
    watch(() => reactData.tableColumn, () => {
      tableColumnFlag.value++
    })
    watch(tableColumnFlag, () => {
      $xeTable.analyColumnWidth()
    })

    watch(() => reactData.upDataFlag, () => {
      nextTick(() => {
        $xeTable.updateData()
      })
    })

    watch(() => reactData.reColumnFlag, () => {
      nextTick(() => {
        $xeTable.refreshColumn()
      })
    })

    const reLayoutFlag = ref(0)
    watch(computeSize, () => {
      reLayoutFlag.value++
    })
    watch(() => props.showHeader, () => {
      reLayoutFlag.value++
    })
    watch(() => props.showFooter, () => {
      reLayoutFlag.value++
    })
    watch(() => props.showHeaderOverflow, () => {
      reLayoutFlag.value++
    })
    watch(() => props.showOverflow, () => {
      updateColumnAllOverflow()
      reLayoutFlag.value++
    })
    watch(() => props.showFooterOverflow, () => {
      reLayoutFlag.value++
    })
    watch(() => reactData.overflowX, () => {
      reLayoutFlag.value++
    })
    watch(() => reactData.overflowY, () => {
      reLayoutFlag.value++
    })
    watch(() => props.height, () => {
      reLayoutFlag.value++
    })
    watch(() => props.maxHeight, () => {
      reLayoutFlag.value++
    })
    watch(computeScrollbarXToTop, () => {
      reLayoutFlag.value++
    })
    watch(computeScrollbarYToLeft, () => {
      reLayoutFlag.value++
    })
    watch(() => VxeUI.getLanguage(), () => {
      reLayoutFlag.value++
    })
    watch(() => {
      const scrollbarXOpts = computeScrollbarXOpts.value
      return scrollbarXOpts.visible
    }, () => {
      reLayoutFlag.value++
    })
    watch(() => {
      const scrollbarYOpts = computeScrollbarYOpts.value
      return scrollbarYOpts.visible
    }, () => {
      reLayoutFlag.value++
    })
    watch(reLayoutFlag, () => {
      $xeTable.recalculate(true)
    })

    const footFlag = ref(0)
    watch(() => props.footerData ? props.footerData.length : -1, () => {
      footFlag.value++
    })
    watch(() => props.footerData, () => {
      footFlag.value++
    })
    watch(footFlag, () => {
      internalData.footerFullDataRowData = {}
      $xeTable.updateFooter()
    })

    watch(() => props.syncResize, (value) => {
      if (value) {
        handleUpdateResize()
        nextTick(() => {
          handleUpdateResize()
          setTimeout(() => handleUpdateResize())
        })
      }
    })

    const mergeCellFlag = ref(0)
    watch(() => props.mergeCells ? props.mergeCells.length : -1, () => {
      mergeCellFlag.value++
    })
    watch(() => props.mergeCells, () => {
      mergeCellFlag.value++
    })
    watch(mergeCellFlag, () => {
      handleUpdateMergeBodyCells(props.mergeCells || [])
    })

    const mergeHeaderItemFlag = ref(0)
    watch(() => props.mergeHeaderCells ? props.mergeHeaderCells.length : -1, () => {
      mergeHeaderItemFlag.value++
    })
    watch(() => props.mergeHeaderCells, () => {
      mergeHeaderItemFlag.value++
    })
    watch(mergeHeaderItemFlag, () => {
      handleUpdateMergeHeaderCells(props.mergeHeaderCells || [])
    })

    const mergeFooteCellFlag = ref(0)
    watch(() => props.mergeFooterCells ? props.mergeFooterCells.length : -1, () => {
      mergeFooteCellFlag.value++
    })
    watch(() => props.mergeFooterCells, () => {
      mergeFooteCellFlag.value++
    })
    watch(() => props.mergeFooterItems ? props.mergeFooterItems.length : -1, () => {
      mergeFooteCellFlag.value++
    })
    watch(() => props.mergeFooterItems, () => {
      mergeFooteCellFlag.value++
    })
    watch(mergeFooteCellFlag, () => {
      const mFooterCells = props.mergeFooterCells || props.mergeFooterItems
      handleUpdateMergeFooterCells(mFooterCells || [])
    })

    watch(computeRowGroupFields, (val) => {
      handleUpdateRowGroup(val)
    })

    watch(computeRowField, () => {
      const { inited, tableFullData } = internalData
      // 行主键被改变，重载表格
      if (inited) {
        handleKeyField()
        reactData.tableData = []
        nextTick(() => {
          $xeTable.reloadData(tableFullData)
        })
      }
    })

    const kfFlag = ref(0)
    watch(() => reactData.updateColFlag, () => {
      kfFlag.value++
    })
    watch(computeKeepFields, () => {
      kfFlag.value++
    })
    watch(kfFlag, () => {
      const keepFields = computeKeepFields.value
      const kpfMaps: Record<string, number> = {}
      keepFields.forEach(field => {
        kpfMaps[field] = 1
      })
      internalData.keepUpdateFieldMaps = kpfMaps
    })

    if ($xeTabs) {
      watch(() => $xeTabs ? $xeTabs.reactData.resizeFlag : null, () => {
        handleGlobalResizeEvent()
      })
    }

    handleKeyField()

    hooks.forEach((options) => {
      const { setupTable } = options
      if (setupTable) {
        const hookRest = setupTable($xeTable)
        if (hookRest && XEUtils.isObject(hookRest)) {
          Object.assign($xeTable, hookRest)
        }
      }
    })

    $xeTable.preventEvent(null, 'created', { $table: $xeTable })

    let resizeObserver: ResizeObserver

    onActivated(() => {
      $xeTable.recalculate().then(() => $xeTable.refreshScroll())
      $xeTable.preventEvent(null, 'activated', { $table: $xeTable })
    })

    onDeactivated(() => {
      const { filterStore } = reactData
      if (filterStore.visible) {
        $xeTable.clearFilter()
      }
      $xeTable.closeTooltip()
      internalData.isActivated = false
      $xeTable.preventEvent(null, 'deactivated', { $table: $xeTable })
    })

    onMounted(() => {
      const { exportConfig, importConfig, treeConfig, minHeight } = props
      const { scrollXStore, scrollYStore } = internalData
      const columnOpts = computeColumnOpts.value
      const columnDragOpts = computeColumnDragOpts.value
      const aggregateOpts = computeAggregateOpts.value
      const virtualYOpts = computeVirtualYOpts.value
      const editOpts = computeEditOpts.value
      const treeOpts = computeTreeOpts.value
      const radioOpts = computeRadioOpts.value
      const checkboxOpts = computeCheckboxOpts.value
      const expandOpts = computeExpandOpts.value
      const rowOpts = computeRowOpts.value
      const customOpts = computeCustomOpts.value
      const mouseOpts = computeMouseOpts.value
      const exportOpts = computeExportOpts.value
      const importOpts = computeImportOpts.value
      // const currentRowOpts = computeCurrentRowOpts.value
      // const currentColumnOpts = computeCurrentColumnOpts.value
      // const keyboardOpts = computeKeyboardOpts.value
      const rowDragOpts = computeRowDragOpts.value
      const areaOpts = computeAreaOpts.value
      const sortOpts = computeSortOpts.value
      const filterOpts = computeFilterOpts.value
      const { groupFields } = aggregateOpts

      if ($xeGantt) {
        const { refClassifyWrapperElem } = $xeGantt.getRefMaps()
        const classifyWrapperEl = refClassifyWrapperElem.value
        if (classifyWrapperEl) {
          internalData.teleportToWrapperElem = classifyWrapperEl
        }
      }
      if ($xeGGWrapper) {
        const { refPopupContainerElem } = $xeGGWrapper.getRefMaps()
        const popupContainerEl = refPopupContainerElem.value
        if (popupContainerEl) {
          internalData.popupToWrapperElem = popupContainerEl
        }
      }

      if (columnOpts.drag || rowOpts.drag || customOpts.allowSort) {
        initTpImg()
      }

      if (props.rowId) {
        warnLog('vxe.error.delProp', ['row-id', 'row-config.keyField'])
      }
      if (props.rowKey) {
        warnLog('vxe.error.delProp', ['row-key', 'row-config.useKey'])
      }
      if (props.columnKey) {
        warnLog('vxe.error.delProp', ['column-id', 'column-config.useKey'])
      }
      if (!(props.rowId || rowOpts.keyField) && (checkboxOpts.reserve || checkboxOpts.checkRowKeys || radioOpts.reserve || radioOpts.checkRowKey || expandOpts.expandRowKeys || treeOpts.expandRowKeys)) {
        warnLog('vxe.error.reqProp', ['row-config.keyField'])
      }
      if (props.editConfig && (editOpts.showStatus || editOpts.showUpdateStatus || editOpts.showInsertStatus) && !props.keepSource) {
        warnLog('vxe.error.reqProp', ['keep-source'])
      }
      // if (treeConfig && (treeOpts.showLine || treeOpts.line) && !showOverflow) {
      //   warnLog('vxe.error.reqProp', ['show-overflow'])
      // }
      if (treeConfig && !treeOpts.transform && props.stripe) {
        warnLog('vxe.error.noTree', ['stripe'])
      }
      if (treeConfig && !treeOpts.transform) {
        if (sortOpts.isDeep) {
          warnLog('vxe.error.reqSupportProp', ['transform=false', 'sort-config.isDeep=false'])
        }
        if (filterOpts.isDeep) {
          warnLog('vxe.error.reqSupportProp', ['transform=false', 'filter-config.isDeep=false'])
        }
      }
      if (props.showFooter && !(props.footerMethod || props.footerData)) {
        warnLog('vxe.error.reqProp', ['footer-data | footer-method'])
      }
      if (rowOpts.height) {
        warnLog('vxe.error.delProp', ['row-config.height', 'cell-config.height'])
      }
      if (props.highlightCurrentRow) {
        warnLog('vxe.error.delProp', ['highlight-current-row', 'row-config.isCurrent'])
      }
      if (props.highlightHoverRow) {
        warnLog('vxe.error.delProp', ['highlight-hover-row', 'row-config.isHover'])
      }
      if (props.highlightCurrentColumn) {
        warnLog('vxe.error.delProp', ['highlight-current-column', 'column-config.isCurrent'])
      }
      if (props.highlightHoverColumn) {
        warnLog('vxe.error.delProp', ['highlight-hover-column', 'column-config.isHover'])
      }
      if (props.resizable) {
        warnLog('vxe.error.delProp', ['resizable', 'column-config.resizable'])
      }
      // if (props.virtualXConfig && props.scrollX) {
      //   warnLog('vxe.error.notSupportProp', ['virtual-x-config', 'scroll-x', 'scroll-x=null'])
      // }
      // if (props.virtualYConfig && props.scrollY) {
      //   warnLog('vxe.error.notSupportProp', ['virtual-y-config', 'scroll-y', 'scroll-y=null'])
      // }
      if (props.aggregateConfig && props.rowGroupConfig) {
        warnLog('vxe.error.notSupportProp', ['aggregate-config', 'row-group-config', 'row-group-config=null'])
      }

      // if (props.scrollY) {
      //   warnLog('vxe.error.delProp', ['scroll-y', 'virtual-y-config'])
      // }
      // if (props.scrollX) {
      //   warnLog('vxe.error.delProp', ['scroll-x', 'virtual-x-config'])
      // }
      // 检查导入导出类型，如果自定义导入导出方法，则不校验类型
      if (importConfig && importOpts.types && !importOpts.importMethod && !XEUtils.includeArrays(XEUtils.keys(importOpts._typeMaps), importOpts.types)) {
        warnLog('vxe.error.errProp', [`export-config.types=${importOpts.types.join(',')}`, importOpts.types.filter((type) => XEUtils.includes(XEUtils.keys(importOpts._typeMaps), type)).join(',') || XEUtils.keys(importOpts._typeMaps).join(',')])
      }
      if (exportConfig && exportOpts.types && !exportOpts.exportMethod && !XEUtils.includeArrays(XEUtils.keys(exportOpts._typeMaps), exportOpts.types)) {
        warnLog('vxe.error.errProp', [`export-config.types=${exportOpts.types.join(',')}`, exportOpts.types.filter((type) => XEUtils.includes(XEUtils.keys(exportOpts._typeMaps), type)).join(',') || XEUtils.keys(exportOpts._typeMaps).join(',')])
      }

      if (!props.id) {
        if ((props.customConfig ? isEnableConf(customOpts) : customOpts.enabled) && customOpts.storage) {
          errLog('vxe.error.reqProp', ['id'])
        }
      }
      if (props.treeConfig && checkboxOpts.range) {
        errLog('vxe.error.noTree', ['checkbox-config.range'])
      }
      if (rowOpts.height && !props.showOverflow) {
        warnLog('vxe.error.notProp', ['table.show-overflow'])
      }
      if (!$xeTable.triggerClAreaModnEvent) {
        if (props.areaConfig) {
          warnLog('vxe.error.notProp', ['area-config'])
        }
        if (props.clipConfig) {
          warnLog('vxe.error.notProp', ['clip-config'])
        }
        if (props.fnrConfig) {
          warnLog('vxe.error.notProp', ['fnr-config'])
        }
        if (mouseOpts.area) {
          errLog('vxe.error.notProp', ['mouse-config.area'])
          return
        }
        if (mouseOpts.area && areaOpts.selectCellByHeader && columnOpts.drag && columnDragOpts.trigger === 'cell') {
          errLog('vxe.error.notSupportProp', ['area-config.selectCellByHeader & column-config.drag', 'column-drag-config.trigger=cell', 'column-drag-config.trigger=default | area-config.selectCellByHeader=false'])
        }
      }
      if (!$xeTable.handlePivotTableAggregateData) {
        if (customOpts.allowGroup) {
          errLog('vxe.error.notProp', ['custom-config.allowGroup'])
          return
        }
        if (customOpts.allowValues) {
          errLog('vxe.error.notProp', ['custom-config.allowValues'])
          return
        }
      }
      if (treeConfig && rowOpts.drag && !treeOpts.transform) {
        errLog('vxe.error.notSupportProp', ['row-config.drag', 'tree-config.transform=false', 'tree-config.transform=true'])
      }
      if (treeConfig && rowDragOpts.isCrossTableDrag && !rowDragOpts.isCrossDrag) {
        errLog('vxe.error.reqSupportProp', ['tree-config & row-drag-config.isCrossTableDrag', 'row-drag-config.isCrossDrag'])
      }
      if (props.dragConfig) {
        warnLog('vxe.error.delProp', ['drag-config', 'row-drag-config'])
      }
      if (props.rowGroupConfig) {
        warnLog('vxe.error.delProp', ['row-group-config', 'aggregate-config'])
      }
      if (aggregateOpts.countFields) {
        warnLog('vxe.error.delProp', ['row-group-config.countFields', 'column.agg-func'])
      }
      if (aggregateOpts.aggregateMethod) {
        warnLog('vxe.error.delProp', ['row-group-config.aggregateMethod', 'aggregate-config.calcValuesMethod'])
      }
      if (aggregateOpts.countMethod) {
        warnLog('vxe.error.delProp', ['aggregate-config.countMethod', 'aggregate-config.calcValuesMethod'])
      }
      if (props.treeConfig && treeOpts.children) {
        warnLog('vxe.error.delProp', ['tree-config.children', 'tree-config.childrenField'])
      }
      if (props.treeConfig && treeOpts.line) {
        warnLog('vxe.error.delProp', ['tree-config.line', 'tree-config.showLine'])
      }
      if (mouseOpts.area && mouseOpts.selected) {
        warnLog('vxe.error.errConflicts', ['mouse-config.area', 'mouse-config.selected'])
      }
      if (mouseOpts.area && (props.treeConfig && !treeOpts.transform)) {
        errLog('vxe.error.noTree', ['mouse-config.area'])
      }
      if (props.editConfig && editOpts.activeMethod) {
        warnLog('vxe.error.delProp', ['edit-config.activeMethod', 'edit-config.beforeEditMethod'])
      }
      if (props.treeConfig && checkboxOpts.isShiftKey) {
        errLog('vxe.error.errConflicts', ['tree-config', 'checkbox-config.isShiftKey'])
      }
      if (checkboxOpts.halfField) {
        warnLog('vxe.error.delProp', ['checkbox-config.halfField', 'checkbox-config.indeterminateField'])
      }
      if (props.editConfig && isEnableConf(editOpts) && props.editRules && (minHeight === 0 || minHeight === '0')) {
        warnLog('vxe.error.reqSupportProp', ['edit-config & edit-rules', 'min-height'])
      }

      if (treeConfig) {
        XEUtils.arrayEach(['rowField', 'parentField', 'childrenField', 'hasChildField', 'mapChildrenField'], key => {
          const val = treeOpts[key as 'rowField']
          if (val && val.indexOf('.') > -1) {
            errLog('vxe.error.errProp', [`${key}=${val}`, `${key}=${val.split('.')[0]}`])
          }
        })
      }

      if (rowOpts.currentMethod) {
        warnLog('vxe.error.delProp', ['row-config.currentMethod', 'current-row-config.beforeSelectMethod'])
      }
      if (columnOpts.currentMethod) {
        warnLog('vxe.error.delProp', ['row-config.currentMethod', 'current-column-config.beforeSelectMethod'])
      }
      // if ((rowOpts.isCurrent || highlightCurrentRow) && props.keyboardConfig && keyboardOpts.isArrow && !XEUtils.isBoolean(currentRowOpts.isFollowSelected)) {
      //   warnLog('vxe.error.notConflictProp', ['row-config.isCurrent & keyboard-config.isArrow', 'current-row-config.isFollowSelected'])
      // }
      // if ((columnOpts.isCurrent || highlightCurrentColumn) && props.keyboardConfig && keyboardOpts.isArrow && !XEUtils.isBoolean(currentColumnOpts.isFollowSelected)) {
      //   warnLog('vxe.error.notConflictProp', ['column-config.isCurrent & keyboard-config.isArrow', 'current-column-config.isFollowSelected'])
      // }

      // 如果不支持虚拟滚动
      // if (props.spanMethod) {
      //   if (virtualXOpts.enabled) {
      //     warnLog('vxe.error.notConflictProp', ['span-method', 'virtual-x-config.enabled=false'])
      //   }
      //   if (virtualYOpts.enabled) {
      //     warnLog('vxe.error.notConflictProp', ['span-method', 'virtual-y-config.enabled=false'])
      //   }
      // }
      // if (props.footerSpanMethod) {
      //   if (virtualXOpts.enabled) {
      //     warnLog('vxe.error.notConflictProp', ['footer-span-method', 'virtual-x-config.enabled=false'])
      //   }
      // }

      // 检查是否有安装需要的模块
      // if (props.editConfig && !$xeTable.insert) {
      //   errLog('vxe.error.reqModule', ['Edit'])
      // }
      // if (props.editRules && !$xeTable.validate) {
      //   errLog('vxe.error.reqModule', ['Validator'])
      // }
      // if ((checkboxOpts.range || props.keyboardConfig || props.mouseConfig) && !$xeTable.handleCellMousedownEvent) {
      //   errLog('vxe.error.reqModule', ['Keyboard'])
      // }
      // if ((props.printConfig || props.importConfig || props.exportConfig) && !$xeTable.exportData) {
      //   errLog('vxe.error.reqModule', ['Export'])
      // }

      Object.assign(scrollYStore, {
        startIndex: 0,
        endIndex: 0,
        visibleSize: 0
      })
      Object.assign(scrollXStore, {
        startIndex: 0,
        endIndex: 0,
        visibleSize: 0
      })

      handleUpdateRowGroup(groupFields)

      initData()

      nextTick(() => {
        if (props.autoResize) {
          const el = refElem.value
          const parentEl = $xeTable.getParentElem()
          resizeObserver = globalResize.create(() => {
            if (props.autoResize) {
              handleResizeEvent()
            }
          })
          if (el) {
            resizeObserver.observe(el)
          }
          if (parentEl) {
            resizeObserver.observe(parentEl)
          }
        }
      })

      if (virtualYOpts.mode !== 'scroll') {
        const tableViewportEl = refTableViewportElem.value
        if (tableViewportEl) {
          tableViewportEl.addEventListener('wheel', $xeTable.triggerBodyWheelEvent, { passive: false })
        }
      }

      globalEvents.on($xeTable, 'paste', handleGlobalPasteEvent)
      globalEvents.on($xeTable, 'copy', handleGlobalCopyEvent)
      globalEvents.on($xeTable, 'cut', handleGlobalCutEvent)
      globalEvents.on($xeTable, 'mousedown', handleGlobalMousedownEvent)
      globalEvents.on($xeTable, 'blur', handleGlobalBlurEvent)
      globalEvents.on($xeTable, 'mousewheel', handleGlobalMousewheelEvent)
      globalEvents.on($xeTable, 'keydown', handleGlobalKeydownEvent)
      globalEvents.on($xeTable, 'resize', handleGlobalResizeEvent)
      globalEvents.on($xeTable, 'scroll', handleGlobalScrollEvent)
      globalEvents.on($xeTable, 'contextmenu', $xeTable.handleGlobalContextmenuEvent)
      $xeTable.preventEvent(null, 'mounted', { $table: $xeTable })
    })

    onBeforeUnmount(() => {
      const { _sToTime } = internalData
      if (_sToTime) {
        clearTimeout(_sToTime)
      }

      const tableViewportEl = refTableViewportElem.value
      if (tableViewportEl) {
        tableViewportEl.removeEventListener('wheel', $xeTable.triggerBodyWheelEvent)
      }

      internalData.cvCacheMaps = {}
      internalData.prevDragRow = null
      internalData.prevDragCol = null

      if (resizeObserver) {
        resizeObserver.disconnect()
      }

      $xeTable.closeTooltip()
      $xeTable.closeFilter()
      if ($xeTable.closeMenu) {
        $xeTable.closeMenu()
      }

      globalEvents.off($xeTable, 'paste')
      globalEvents.off($xeTable, 'copy')
      globalEvents.off($xeTable, 'cut')
      globalEvents.off($xeTable, 'mousedown')
      globalEvents.off($xeTable, 'blur')
      globalEvents.off($xeTable, 'mousewheel')
      globalEvents.off($xeTable, 'keydown')
      globalEvents.off($xeTable, 'resize')
      globalEvents.off($xeTable, 'scroll')
      globalEvents.off($xeTable, 'contextmenu')

      $xeTable.preventEvent(null, 'beforeUnmount', { $table: $xeTable })
      XEUtils.assign(reactData, createReactData())
      XEUtils.assign(internalData, createInternalData())
    })

    onUnmounted(() => {
      $xeTable.preventEvent(null, 'unmounted', { $table: $xeTable })
    })

    nextTick(() => {
      if (props.loading) {
        if (!VxeUILoadingComponent && !slots.loading) {
          errLog('vxe.error.errProp', ['loading=true', 'loading=false | <template #loading>...</template>'])
          errLog('vxe.error.reqComp', ['vxe-loading'])
        }
      }
      if ((props.showOverflow === true || props.showOverflow === 'tooltip') ||
          (props.showHeaderOverflow === true || props.showHeaderOverflow === 'tooltip') ||
          (props.showFooterOverflow === true || props.showFooterOverflow === 'tooltip') ||
          props.tooltipConfig || props.editRules) {
        if (!VxeUITooltipComponent) {
          if (props.showOverflow === true) {
            errLog('vxe.error.errProp', ['show-overflow=true', 'show-overflow=title'])
          }
          if (props.showOverflow === 'tooltip') {
            errLog('vxe.error.errProp', ['show-overflow=tooltip', 'show-overflow=title'])
          }
          if (props.showHeaderOverflow === true) {
            errLog('vxe.error.errProp', ['show-header-overflow=true', 'show-header-overflow=title'])
          }
          if (props.showHeaderOverflow === 'tooltip') {
            errLog('vxe.error.errProp', ['show-header-overflow=tooltip', 'show-header-overflow=title'])
          }
          if (props.showFooterOverflow === true) {
            errLog('vxe.error.errProp', ['show-footer-overflow=true', 'show-footer-overflow=title'])
          }
          if (props.showFooterOverflow === 'tooltip') {
            errLog('vxe.error.errProp', ['show-footer-overflow=tooltip', 'show-footer-overflow=title'])
          }
          errLog('vxe.error.reqComp', ['vxe-tooltip'])
        }
      }
    })

    provide('$xeColgroup', null)
    provide('$xeTable', $xeTable)

    $xeTable.renderVN = renderVN

    return $xeTable
  },
  render () {
    return this.renderVN()
  }
})
