UNPKG

vxe-table

Version:

A PC-end table component based on Vxe UI, supporting copy-paste, data pivot table, and high-performance virtual list table solution.

744 lines (684 loc) 26.2 kB
import { watch, reactive } from 'vue' import XEUtils from 'xe-utils' import { ColumnInfo } from './columnInfo' import { isPx, isScale, queryElement } from '../../ui/src/dom' import { eqEmptyValue } from '../../ui/src/utils' import type { VxeTableConstructor, VxeTablePrivateMethods, VxeTableDefines, VxeTablePropTypes, TableInternalData } from '../../../types' export function createInternalData (): TableInternalData { return { tZindex: 0, currKeyField: '', isCurrDeepKey: false, elemStore: {}, // 存放横向 X 虚拟滚动相关的信息 scrollXStore: { preloadSize: 0, offsetSize: 0, visibleSize: 0, visibleStartIndex: 0, visibleEndIndex: 0, startIndex: 0, endIndex: 0 }, // 存放纵向 Y 虚拟滚动相关信息 scrollYStore: { preloadSize: 0, offsetSize: 0, visibleSize: 0, visibleStartIndex: 0, visibleEndIndex: 0, startIndex: 0, endIndex: 0 }, // 表格宽度 tableWidth: 0, // 表格高度 tableHeight: 0, customHeight: 0, customMinHeight: 0, customMaxHeight: 0, // 当前 hover 行 hoverRow: null, // 最后滚动位置 lastScrollLeft: 0, lastScrollTop: 0, // 单选框属性,已选中保留的行 radioReserveRow: null, // 复选框属性,已选中保留的行集合 checkboxReserveRowMap: {}, // 行数据,已展开保留的行集合 rowExpandedReserveRowMap: {}, // 树结构数据,已展开保留的行集合 treeExpandedReserveRowMap: {}, // 树结构数据,不确定状态的集合 treeIndeterminateRowMaps: {}, // 列表完整数据、条件处理后 tableFullData: [], afterFullData: [], afterTreeFullData: [], afterGroupFullData: [], // 列表条件处理后数据集合 afterFullRowMaps: {}, // 树结构完整数据、条件处理后 tableFullTreeData: [], // 行分组全量数据、条件处理后 tableFullGroupData: [], tableSynchData: [], tableSourceData: [], // 收集的列配置(带分组) collectColumn: [], // 完整所有列(不带分组) tableFullColumn: [], // 渲染所有列 visibleColumn: [], // 全量数据集(包括当前和已删除) fullAllDataRowIdData: {}, // 数据集(仅当前) fullDataRowIdData: {}, // 数据集(仅可视) visibleDataRowIdData: {}, // 渲染中缓存数据 sourceDataRowIdData: {}, fullColumnIdData: {}, fullColumnFieldData: {}, // 合并单元格的数据 mergeBodyList: [], mergeBodyMaps: {}, // 合并表尾的数据 mergeFooterList: [], mergeFooterMaps: {}, // 已合并单元格数据集合 mergeBodyCellMaps: {}, // 已合并表尾数据集合 mergeFooterCellMaps: {}, // 已展开的行集合 rowExpandedMaps: {}, // 懒加载中的展开行的集合 rowExpandLazyLoadedMaps: {}, // 已展开的分组行 rowGroupExpandedMaps: {}, // 已展开树节点集合 treeExpandedMaps: {}, // 懒加载中的树节点的集合 treeExpandLazyLoadedMaps: {}, // 复选框属性,已选中的行集合 selectCheckboxMaps: {}, // 已标记的对象集 pendingRowMaps: {}, // 已新增的临时行 insertRowMaps: {}, // 已删除行 removeRowMaps: {}, cvCacheMaps: {}, // 表头高度 tHeaderHeight: 0, // 表体高度 tBodyHeight: 0, // 表尾高度 tFooterHeight: 0, inited: false, tooltipTimeout: null, initStatus: false, isActivated: false } } const getAllConvertColumns = (columns: any, parentColumn?: any) => { const result: any[] = [] columns.forEach((column: any) => { column.parentId = parentColumn ? parentColumn.id : null if (column.visible) { if (column.children && column.children.length && column.children.some((column: any) => column.visible)) { result.push(column) result.push(...getAllConvertColumns(column.children, column)) } else { result.push(column) } } }) return result } export const convertHeaderColumnToRows = (originColumns: any): any[][] => { let maxLevel = 1 const traverse = (column: any, parent?: any) => { if (parent) { column.level = parent.level + 1 if (maxLevel < column.level) { maxLevel = column.level } } if (column.children && column.children.length && column.children.some((column: any) => column.visible)) { let colSpan = 0 column.children.forEach((subColumn: any) => { if (subColumn.visible) { traverse(subColumn, column) colSpan += subColumn.colSpan } }) column.colSpan = colSpan } else { column.colSpan = 1 } } originColumns.forEach((column: any) => { column.level = 1 traverse(column) }) const rows: any[] = [] for (let i = 0; i < maxLevel; i++) { rows.push([]) } const allColumns = getAllConvertColumns(originColumns) allColumns.forEach((column) => { if (column.children && column.children.length && column.children.some((column: any) => column.visible)) { column.rowSpan = 1 } else { column.rowSpan = maxLevel - column.level + 1 } rows[column.level - 1].push(column) }) return rows } export function restoreScrollLocation ($xeTable: VxeTableConstructor, scrollLeft: number, scrollTop: number) { const internalData = $xeTable.internalData if (scrollLeft || scrollTop) { internalData.intoRunScroll = false internalData.inVirtualScroll = false internalData.inWheelScroll = false internalData.inHeaderScroll = false internalData.inBodyScroll = false internalData.inFooterScroll = false internalData.scrollRenderType = '' // 还原滚动状态 return $xeTable.scrollTo(scrollLeft, scrollTop) } return $xeTable.clearScroll() } /** * 生成行的唯一主键 */ export function getRowUniqueId () { return XEUtils.uniqueId('row_') } export function hasDeepKey (rowKey: string) { return rowKey.indexOf('.') > -1 } // 行主键 key export function getRowkey ($xeTable: VxeTableConstructor) { const { currKeyField } = $xeTable.internalData return currKeyField } // 行主键 value export function getRowid ($xeTable: VxeTableConstructor, row: any) { const internalData = $xeTable.internalData const { isCurrDeepKey, currKeyField } = internalData return row ? encodeRowid((isCurrDeepKey ? getDeepRowIdByKey : getFastRowIdByKey)(row, currKeyField)) : '' } export function createHandleUpdateRowId ($xeTable: VxeTableConstructor) { const internalData = $xeTable.internalData const { isCurrDeepKey, currKeyField } = internalData const updateRId = isCurrDeepKey ? updateDeepRowKey : updateFastRowKey return { rowKey: currKeyField, handleUpdateRowId (row: any) { return row ? updateRId(row, currKeyField) : '' } } } export function createHandleGetRowId ($xeTable: VxeTableConstructor) { const internalData = $xeTable.internalData const { isCurrDeepKey, currKeyField } = internalData const getRId = isCurrDeepKey ? getDeepRowIdByKey : getFastRowIdByKey return { rowKey: currKeyField, handleGetRowId (row: any) { return row ? encodeRowid(getRId(row, currKeyField)) : '' } } } // 编码行主键 export function encodeRowid (rowVal: string) { return XEUtils.eqNull(rowVal) ? '' : encodeURIComponent(rowVal) } function getDeepRowIdByKey (row: any, rowKey: string) { return XEUtils.get(row, rowKey) } export function updateDeepRowKey (row: any, rowKey: string) { let rowid = encodeRowid(getDeepRowIdByKey(row, rowKey)) if (eqEmptyValue(rowid)) { rowid = getRowUniqueId() XEUtils.set(row, rowKey, rowid) } return rowid } function getFastRowIdByKey (row: any, rowKey: string) { return row[rowKey] } export function updateFastRowKey (row: any, rowKey: string) { let rowid = encodeRowid(getFastRowIdByKey(row, rowKey)) if (eqEmptyValue(rowid)) { rowid = getRowUniqueId() row[rowKey] = rowid } return rowid } export interface XEColumnInstance { columnConfig: ColumnInfo; } export function handleFieldOrColumn ($xeTable: VxeTableConstructor, fieldOrColumn: string | VxeTableDefines.ColumnInfo | null) { if (fieldOrColumn) { return XEUtils.isString(fieldOrColumn) || XEUtils.isNumber(fieldOrColumn) ? $xeTable.getColumnByField(`${fieldOrColumn}`) : fieldOrColumn } return null } export function handleRowidOrRow ($xeTable: VxeTableConstructor, rowidOrRow: any) { if (rowidOrRow) { const rowid = XEUtils.isString(rowidOrRow) || XEUtils.isNumber(rowidOrRow) ? rowidOrRow : getRowid($xeTable, rowidOrRow) return $xeTable.getRowById(rowid) } return null } export function getCellRestHeight (rowRest: VxeTableDefines.RowCacheItem, cellOpts: VxeTablePropTypes.CellConfig, rowOpts: VxeTablePropTypes.RowConfig, defaultRowHeight: number) { return rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight } function getPaddingLeftRightSize (elem: HTMLElement | null) { if (elem) { const computedStyle = getComputedStyle(elem) const paddingLeft = XEUtils.toNumber(computedStyle.paddingLeft) const paddingRight = XEUtils.toNumber(computedStyle.paddingRight) return paddingLeft + paddingRight } return 0 } function getElementMarginAndWidth (elem: HTMLElement | null) { if (elem) { const computedStyle = getComputedStyle(elem) const marginLeft = XEUtils.toNumber(computedStyle.marginLeft) const marginRight = XEUtils.toNumber(computedStyle.marginRight) return elem.offsetWidth + marginLeft + marginRight } return 0 } export function toFilters (filters: any) { if (filters && XEUtils.isArray(filters)) { return filters.map(({ label, value, data, resetValue, checked }) => { return { label, value, data, resetValue, checked: !!checked, _checked: !!checked } }) } return filters } export function toTreePathSeq (path: any[]) { return path.map((num, i) => i % 2 === 0 ? (Number(num) + 1) : '.').join('') } export function getCellValue (row: any, column: VxeTableDefines.ColumnInfo) { return XEUtils.get(row, column.field) } export function setCellValue (row: any, column: VxeTableDefines.ColumnInfo, value: any) { return XEUtils.set(row, column.field, value) } export function getRefElem (refEl: any) { if (refEl) { const rest = refEl.value if (rest) { return (rest.$el || rest) as HTMLElement } } return null } export function getCalcHeight (height: number | 'unset' | undefined | null) { if (height === 'unset') { return 0 } return height || 0 } /** * 列宽拖动最大宽度 * @param params * @returns */ export function getColReMaxWidth (params: { $table: VxeTableConstructor & VxeTablePrivateMethods; column: VxeTableDefines.ColumnInfo; columnIndex: number; $columnIndex: number; $rowIndex: number; cell: HTMLTableCellElement; }) { const { $table } = params const { computeResizableOpts } = $table.getComputeMaps() const resizableOpts = computeResizableOpts.value const { maxWidth: reMaxWidth } = resizableOpts // 如果自定义调整宽度逻辑 if (reMaxWidth) { const customMaxWidth = XEUtils.isFunction(reMaxWidth) ? reMaxWidth(params) : reMaxWidth if (customMaxWidth !== 'auto') { return Math.max(1, XEUtils.toNumber(customMaxWidth)) } } return -1 } /** * 列宽拖动最小宽度 * @param params * @returns */ export function getColReMinWidth (params: { $table: VxeTableConstructor & VxeTablePrivateMethods; column: VxeTableDefines.ColumnInfo; columnIndex: number; $columnIndex: number; $rowIndex: number; cell: HTMLTableCellElement; }) { const { $table, column, cell } = params const tableProps = $table.props const internalData = $table.internalData const { computeResizableOpts } = $table.getComputeMaps() const resizableOpts = computeResizableOpts.value const { minWidth: reMinWidth } = resizableOpts // 如果自定义调整宽度逻辑 if (reMinWidth) { const customMinWidth = XEUtils.isFunction(reMinWidth) ? reMinWidth(params) : reMinWidth if (customMinWidth !== 'auto') { return Math.max(1, XEUtils.toNumber(customMinWidth)) } } const { elemStore } = internalData const { showHeaderOverflow: allColumnHeaderOverflow } = tableProps const { showHeaderOverflow, minWidth: colMinWidth } = column const headOverflow = XEUtils.isUndefined(showHeaderOverflow) || XEUtils.isNull(showHeaderOverflow) ? allColumnHeaderOverflow : showHeaderOverflow const showEllipsis = headOverflow === 'ellipsis' const showTitle = headOverflow === 'title' const showTooltip = headOverflow === true || headOverflow === 'tooltip' const hasEllipsis = showTitle || showTooltip || showEllipsis const minTitleWidth = XEUtils.floor((XEUtils.toNumber(getComputedStyle(cell).fontSize) || 14) * 1.8) const paddingLeftRight = getPaddingLeftRightSize(cell) + getPaddingLeftRightSize(queryElement(cell, '.vxe-cell')) let mWidth = minTitleWidth + paddingLeftRight // 默认最小宽处理 if (hasEllipsis) { const dragIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--drag-handle')) const checkboxIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--checkbox')) const requiredIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--required-icon')) const editIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--edit-icon')) const prefixIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell-title-prefix-icon')) const suffixIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell-title-suffix-icon')) const sortIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--sort')) const filterIconWidth = getElementMarginAndWidth(queryElement(cell, '.vxe-cell--filter')) mWidth += dragIconWidth + checkboxIconWidth + requiredIconWidth + editIconWidth + prefixIconWidth + suffixIconWidth + filterIconWidth + sortIconWidth } // 如果设置最小宽 if (colMinWidth) { const bodyScrollElem = getRefElem(elemStore['main-body-scroll']) if (bodyScrollElem) { if (isScale(colMinWidth)) { const bodyWidth = bodyScrollElem.clientWidth - 1 const meanWidth = bodyWidth / 100 return Math.max(mWidth, Math.floor(XEUtils.toInteger(colMinWidth) * meanWidth)) } else if (isPx(colMinWidth)) { return Math.max(mWidth, XEUtils.toInteger(colMinWidth)) } } } return mWidth } export function isColumnInfo (column: any): column is ColumnInfo { return column && (column.constructor === ColumnInfo || column instanceof ColumnInfo) } // 获取所有的列,排除分组 export function getColumnList (columns: VxeTableDefines.ColumnInfo[]) { const result: VxeTableDefines.ColumnInfo[] = [] columns.forEach((column) => { result.push(...(column.children && column.children.length ? getColumnList(column.children) : [column])) }) return result } export function createColumn ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, options: VxeTableDefines.ColumnOptions | VxeTableDefines.ColumnInfo, renderOptions: any): any { return isColumnInfo(options) ? options : reactive(new ColumnInfo($xeTable, options, renderOptions)) } export function watchColumn ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, props: any, column: ColumnInfo) { Object.keys(props).forEach(name => { watch(() => props[name], (value: any) => { column.update(name, value) if ($xeTable) { if (name === 'filters') { $xeTable.setFilter(column as any, value) $xeTable.handleUpdateDataQueue() } else if (['visible', 'fixed', 'width', 'minWidth', 'maxWidth'].includes(name)) { $xeTable.handleRefreshColumnQueue() } } }) }) } export function assembleColumn ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, elem: HTMLElement, column: ColumnInfo, colgroup: XEColumnInstance | null) { const { reactData } = $xeTable const { staticColumns } = reactData const parentElem = elem.parentNode const parentColumn = colgroup ? colgroup.columnConfig : null const parentCols = parentColumn ? parentColumn.children : staticColumns if (parentElem && parentCols) { column.defaultParentId = parentColumn ? parentColumn.id : null parentCols.splice(XEUtils.arrayIndexOf(parentElem.children, elem), 0, column) reactData.staticColumns = staticColumns.slice(0) } } export function destroyColumn ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, column: ColumnInfo) { const { reactData } = $xeTable const { staticColumns } = reactData const matchObj = XEUtils.findTree(staticColumns, item => item.id === column.id, { children: 'children' }) if (matchObj) { matchObj.items.splice(matchObj.index, 1) } reactData.staticColumns = staticColumns.slice(0) } export function getRootColumn ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, column: ColumnInfo) { const { internalData } = $xeTable const { fullColumnIdData } = internalData if (!column) { return null } let parentColId = column.parentId while (fullColumnIdData[parentColId]) { const column = fullColumnIdData[parentColId].column parentColId = column.parentId if (!parentColId) { return column } } return column } const lineOffsetSizes = { mini: 3, small: 2, medium: 1, large: 0 } const countTreeExpand = (prevRow: any, params: VxeTableDefines.CellRenderBodyParams) => { let count = 1 if (!prevRow) { return count } const { $table } = params const reactData = $table.reactData const { treeExpandedFlag } = reactData const internalData = $table.internalData const { treeExpandedMaps } = internalData const { computeTreeOpts } = $table.getComputeMaps() const treeOpts = computeTreeOpts.value const { transform, mapChildrenField } = treeOpts const childrenField = treeOpts.children || treeOpts.childrenField const rowChildren = prevRow[transform ? mapChildrenField : childrenField] if (rowChildren && treeExpandedFlag && treeExpandedMaps[getRowid($table, prevRow)]) { for (let index = 0; index < rowChildren.length; index++) { count += countTreeExpand(rowChildren[index], params) } } return count } export const getOffsetSize = ($xeTable: VxeTableConstructor) => { const { computeSize } = $xeTable.getComputeMaps() const vSize = computeSize.value if (vSize) { return lineOffsetSizes[vSize] || 0 } return 0 } export function calcTreeLine (params: VxeTableDefines.CellRenderBodyParams, prevRow: any) { const { $table, row } = params const tableProps = $table.props const tableReactData = $table.reactData const tableInternalData = $table.internalData const { showOverflow } = tableProps const { scrollYLoad } = tableReactData const { fullAllDataRowIdData } = tableInternalData const { computeRowOpts, computeCellOpts, computeDefaultRowHeight } = $table.getComputeMaps() const rowOpts = computeRowOpts.value const cellOpts = computeCellOpts.value const defaultRowHeight = computeDefaultRowHeight.value const rowid = getRowid($table, row) const rowRest = fullAllDataRowIdData[rowid] const currCellHeight = rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight let expandSize = 1 if (prevRow) { expandSize = countTreeExpand(prevRow, params) } let cellHeight = currCellHeight const vnHeight = rowRest.height if (scrollYLoad) { if (!showOverflow) { cellHeight = vnHeight || currCellHeight } } return cellHeight * expandSize - (prevRow ? 1 : (12 - getOffsetSize($table))) } export function clearTableDefaultStatus ($xeTable: VxeTableConstructor & VxeTablePrivateMethods) { const { props, internalData } = $xeTable internalData.initStatus = false $xeTable.clearSort() $xeTable.clearCurrentRow() $xeTable.clearCurrentColumn() $xeTable.clearRadioRow() $xeTable.clearRadioReserve() $xeTable.clearCheckboxRow() $xeTable.clearCheckboxReserve() $xeTable.clearRowExpand() $xeTable.clearTreeExpand() $xeTable.clearTreeExpandReserve() $xeTable.clearPendingRow() if ($xeTable.clearFilter) { $xeTable.clearFilter() } if ($xeTable.clearSelected && (props.keyboardConfig || props.mouseConfig)) { $xeTable.clearSelected() } if ($xeTable.clearCellAreas && props.mouseConfig) { $xeTable.clearCellAreas() $xeTable.clearCopyCellArea() } return $xeTable.clearScroll() } export function clearTableAllStatus ($xeTable: VxeTableConstructor & VxeTablePrivateMethods) { if ($xeTable.clearFilter) { $xeTable.clearFilter() } return clearTableDefaultStatus($xeTable) } export function rowToVisible ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, row: any) { const tableProps = $xeTable.props const reactData = $xeTable.reactData const internalData = $xeTable.internalData const { computeLeftFixedWidth, computeRightFixedWidth, computeRowOpts, computeCellOpts, computeDefaultRowHeight } = $xeTable.getComputeMaps() const { showOverflow } = tableProps const { scrollYLoad, scrollYTop } = reactData const { elemStore, afterFullData, fullAllDataRowIdData, isResizeCellHeight } = internalData const rowOpts = computeRowOpts.value const cellOpts = computeCellOpts.value const defaultRowHeight = computeDefaultRowHeight.value const leftFixedWidth = computeLeftFixedWidth.value const rightFixedWidth = computeRightFixedWidth.value const bodyScrollElem = getRefElem(elemStore['main-body-scroll']) const rowid = getRowid($xeTable, row) if (bodyScrollElem) { const bodyHeight = bodyScrollElem.clientHeight const bodyScrollTop = bodyScrollElem.scrollTop const trElem: HTMLTableRowElement | null = bodyScrollElem.querySelector(`[rowid="${rowid}"]`) if (trElem) { const trOffsetTop = trElem.offsetTop + (scrollYLoad ? scrollYTop : 0) const trHeight = trElem.clientHeight // 检测行是否在可视区中 if (trOffsetTop < bodyScrollTop || trOffsetTop > bodyScrollTop + bodyHeight) { return $xeTable.scrollTo(null, trOffsetTop) } else if (trOffsetTop + trHeight >= bodyHeight + bodyScrollTop) { return $xeTable.scrollTo(null, bodyScrollTop + trHeight) } } else { // 如果是虚拟渲染滚动 if (scrollYLoad) { const isCustomCellHeight = isResizeCellHeight || cellOpts.height || rowOpts.height if (!isCustomCellHeight && showOverflow) { return $xeTable.scrollTo(null, ($xeTable.findRowIndexOf(afterFullData, row) - 1) * defaultRowHeight) } const rowRest = fullAllDataRowIdData[rowid] || {} const rHeight = rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight const scrollTop = rowRest.oTop if (scrollTop < bodyScrollTop) { return $xeTable.scrollTo(null, scrollTop - leftFixedWidth - 1) } return $xeTable.scrollTo(null, (scrollTop + rHeight) - (bodyHeight - rightFixedWidth - 1)) } } } return Promise.resolve() } export function colToVisible ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, column: VxeTableDefines.ColumnInfo, row?: any) { const reactData = $xeTable.reactData const internalData = $xeTable.internalData const { computeLeftFixedWidth, computeRightFixedWidth } = $xeTable.getComputeMaps() const { scrollXLoad, scrollXLeft } = reactData const { elemStore, visibleColumn } = internalData const leftFixedWidth = computeLeftFixedWidth.value const rightFixedWidth = computeRightFixedWidth.value const bodyScrollElem = getRefElem(elemStore['main-body-scroll']) if (column.fixed) { return Promise.resolve() } if (bodyScrollElem) { const bodyWidth = bodyScrollElem.clientWidth const bodyScrollLeft = bodyScrollElem.scrollLeft let tdElem: HTMLTableCellElement | null = null if (row) { const rowid = getRowid($xeTable, row) tdElem = bodyScrollElem.querySelector(`[rowid="${rowid}"] .${column.id}`) } if (!tdElem) { tdElem = bodyScrollElem.querySelector(`.${column.id}`) } if (tdElem) { const tdOffsetLeft = tdElem.offsetLeft + (scrollXLoad ? scrollXLeft : 0) const cellWidth = tdElem.clientWidth // 检测是否在可视区中 if (tdOffsetLeft < (bodyScrollLeft + leftFixedWidth)) { return $xeTable.scrollTo(tdOffsetLeft - leftFixedWidth - 1) } else if ((tdOffsetLeft + cellWidth - bodyScrollLeft) > (bodyWidth - rightFixedWidth)) { return $xeTable.scrollTo((tdOffsetLeft + cellWidth) - (bodyWidth - rightFixedWidth - 1)) } } else { // 检测是否在虚拟渲染可视区中 if (scrollXLoad) { let scrollLeft = 0 const cellWidth = column.renderWidth for (let i = 0; i < visibleColumn.length; i++) { const currCol = visibleColumn[i] if (currCol === column || currCol.id === column.id) { break } scrollLeft += currCol.renderWidth } if (scrollLeft < bodyScrollLeft) { return $xeTable.scrollTo(scrollLeft - leftFixedWidth - 1) } return $xeTable.scrollTo((scrollLeft + cellWidth) - (bodyWidth - rightFixedWidth - 1)) } } } return Promise.resolve() }