UNPKG

vxe-table-select-area

Version:

一个基于 vxe-table 的可区域选中复制、粘贴的组件

1,564 lines (1,541 loc) 173 kB
import XEUtils from 'xe-utils' import GlobalConfig from '../../v-x-e-table/src/conf' import Cell from './cell' import VXETable from '../../v-x-e-table' import { getRowid, getRowkey, clearTableAllStatus, handleFieldOrColumn, restoreScrollLocation, restoreScrollListener, toTreePathSeq, rowToVisible, colToVisible } from './util' import UtilTools, { eqEmptyValue, isEnableConf, getFuncText } from '../../tools/utils' import DomTools, { browse, getPaddingTopBottomSize, setScrollTop, setScrollLeft } from '../../tools/dom' import { formats } from '../../v-x-e-table/src/formats' import { warnLog, errLog } from '../../tools/log' import { getSlotVNs } from '../../tools/vn' const { setCellValue, hasChildrenList, getColumnList } = UtilTools const { calcHeight, hasClass, addClass, removeClass, getEventTargetNode, isNodeElement } = DomTools const isWebkit = browse['-webkit'] && !browse.edge const debounceScrollYDuration = browse.msie ? 80 : 20 const resizableStorageKey = 'VXE_TABLE_CUSTOM_COLUMN_WIDTH' const visibleStorageKey = 'VXE_TABLE_CUSTOM_COLUMN_VISIBLE' const fixedStorageKey = 'VXE_TABLE_CUSTOM_COLUMN_FIXED' const orderStorageKey = 'VXE_TABLE_CUSTOM_COLUMN_ORDER' /** * 生成行的唯一主键 */ function getRowUniqueId () { return XEUtils.uniqueId('row_') } function eqCellValue (row1, row2, field) { 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) } function getNextSortOrder (_vm, column) { const orders = _vm.sortOpts.orders const currOrder = column.order || null const oIndex = orders.indexOf(currOrder) + 1 return orders[oIndex < orders.length ? oIndex : 0] } function getCustomStorageMap (key) { const version = GlobalConfig.version const rest = XEUtils.toStringJSON(localStorage.getItem(key)) return rest && rest._v === version ? rest : { _v: version } } function getRecoverRow (_vm, list) { const { fullAllDataRowMap } = _vm return list.filter(row => fullAllDataRowMap.has(row)) } function handleReserveRow (_vm, reserveRowMap) { const { fullDataRowIdData } = _vm const reserveList = [] XEUtils.each(reserveRowMap, (item, rowid) => { if (fullDataRowIdData[rowid] && reserveList.indexOf(fullDataRowIdData[rowid].row) === -1) { reserveList.push(fullDataRowIdData[rowid].row) } }) return reserveList } function computeVirtualX (_vm) { const { $refs, visibleColumn } = _vm const { tableBody } = $refs const tableBodyElem = tableBody ? tableBody.$el : null if (tableBodyElem) { const { scrollLeft, clientWidth } = tableBodyElem const endWidth = scrollLeft + clientWidth let toVisibleIndex = -1 let cWidth = 0 let visibleSize = 0 for (let colIndex = 0, colLen = visibleColumn.length; colIndex < colLen; colIndex++) { cWidth += visibleColumn[colIndex].renderWidth if (toVisibleIndex === -1 && scrollLeft < cWidth) { toVisibleIndex = colIndex } if (toVisibleIndex >= 0) { visibleSize++ if (cWidth > endWidth) { break } } } return { toVisibleIndex: Math.max(0, toVisibleIndex), visibleSize: Math.max(8, visibleSize) } } return { toVisibleIndex: 0, visibleSize: 8 } } function computeVirtualY (_vm) { const { $refs, vSize, rowHeightMaps } = _vm const { tableHeader, tableBody } = $refs const tableBodyElem = tableBody ? tableBody.$el : null if (tableBodyElem) { const tableHeaderElem = tableHeader ? tableHeader.$el : null let rowHeight = 0 let firstTrElem firstTrElem = tableBodyElem.querySelector('tr') if (!firstTrElem && tableHeaderElem) { firstTrElem = tableHeaderElem.querySelector('tr') } if (firstTrElem) { rowHeight = firstTrElem.clientHeight } if (!rowHeight) { rowHeight = rowHeightMaps[vSize || 'default'] } const visibleSize = Math.max(8, Math.ceil(tableBodyElem.clientHeight / rowHeight) + 2) return { rowHeight, visibleSize } } return { rowHeight: 0, visibleSize: 8 } } function calculateMergerOffserIndex (list, offsetItem, type) { for (let mcIndex = 0, len = list.length; mcIndex < len; mcIndex++) { const mergeItem = list[mcIndex] const { startIndex, endIndex } = offsetItem const mergeStartIndex = mergeItem[type] const mergeSpanNumber = mergeItem[type + 'span'] const mergeEndIndex = mergeStartIndex + mergeSpanNumber if (mergeStartIndex < startIndex && startIndex < mergeEndIndex) { offsetItem.startIndex = mergeStartIndex } if (mergeStartIndex < endIndex && endIndex < mergeEndIndex) { offsetItem.endIndex = mergeEndIndex } if (offsetItem.startIndex !== startIndex || offsetItem.endIndex !== endIndex) { mcIndex = -1 } } } function setMerges (_vm, merges, mList, rowList) { if (merges) { const { treeConfig, visibleColumn } = _vm if (!XEUtils.isArray(merges)) { merges = [merges] } if (treeConfig && merges.length) { errLog('vxe.error.noTree', ['merge-cells | merge-footer-items']) } merges.forEach(item => { let { row, col, rowspan, colspan } = item if (rowList && XEUtils.isNumber(row)) { row = rowList[row] } if (XEUtils.isNumber(col)) { col = visibleColumn[col] } if ((rowList ? row : XEUtils.isNumber(row)) && col && (rowspan || colspan)) { rowspan = XEUtils.toNumber(rowspan) || 1 colspan = XEUtils.toNumber(colspan) || 1 if (rowspan > 1 || colspan > 1) { const mcIndex = XEUtils.findIndexOf(mList, item => item._row === row && item._col === col) const mergeItem = mList[mcIndex] if (mergeItem) { mergeItem.rowspan = rowspan mergeItem.colspan = colspan mergeItem._rowspan = rowspan mergeItem._colspan = colspan } else { const mergeRowIndex = rowList ? rowList.indexOf(row) : row const mergeColIndex = visibleColumn.indexOf(col) mList.push({ row: mergeRowIndex, col: mergeColIndex, rowspan, colspan, _row: row, _col: col, _rowspan: rowspan, _colspan: colspan }) } } } }) } } function removeMerges (_vm, merges, mList, rowList) { const rest = [] if (merges) { const { treeConfig, visibleColumn } = _vm if (!XEUtils.isArray(merges)) { merges = [merges] } if (treeConfig && merges.length) { errLog('vxe.error.noTree', ['merge-cells | merge-footer-items']) } merges.forEach(item => { let { row, col } = item if (rowList && XEUtils.isNumber(row)) { row = rowList[row] } if (XEUtils.isNumber(col)) { col = visibleColumn[col] } const mcIndex = XEUtils.findIndexOf(mList, item => item._row === row && item._col === col) if (mcIndex > -1) { const rItems = mList.splice(mcIndex, 1) rest.push(rItems[0]) } }) } return rest } function clearAllSort (_vm) { _vm.tableFullColumn.forEach((column) => { column.order = null }) } function getOrderField (_vm, column) { const { sortBy, sortType } = column return (row) => { let cellValue if (sortBy) { cellValue = XEUtils.isFunction(sortBy) ? sortBy({ row, column }) : XEUtils.get(row, sortBy) } else { cellValue = _vm.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 Methods = { callSlot (slotFunc, params, h, vNodes) { if (slotFunc) { const { $xegrid } = this if ($xegrid) { return $xegrid.callSlot(slotFunc, params, h, vNodes) } if (XEUtils.isFunction(slotFunc)) { return getSlotVNs(slotFunc.call(this, params, h, vNodes)) } } return [] }, /** * 获取父容器元素 */ getParentElem () { const { $el, $xegrid } = this return $xegrid ? $xegrid.$el.parentNode : $el.parentNode }, /** * 获取父容器的高度 */ getParentHeight () { const { $el, $xegrid, height } = this const parentElem = $el.parentNode const parentPaddingSize = height === 'auto' ? getPaddingTopBottomSize(parentElem) : 0 return Math.floor($xegrid ? $xegrid.getParentHeight() : XEUtils.toNumber(getComputedStyle(parentElem).height) - parentPaddingSize) }, /** * 获取需要排除的高度 * 但渲染表格高度时,需要排除工具栏或分页等相关组件的高度 * 如果存在表尾合计滚动条,则需要排除滚动条高度 */ getExcludeHeight () { const { $xegrid } = this return $xegrid ? $xegrid.getExcludeHeight() : 0 }, /** * 重置表格的一切数据状态 */ clearAll () { return clearTableAllStatus(this) }, /** * 同步 data 数据(即将废弃) * 如果用了该方法,那么组件将不再记录增删改的状态,只能自行实现对应逻辑 * 对于某些特殊的场景,比如深层树节点元素发生变动时可能会用到 */ syncData () { return this.$nextTick().then(() => { this.tableData = [] return this.$nextTick().then(() => this.loadTableData(this.tableFullData)) }) }, /** * 手动处理数据,用于手动排序与筛选 * 对于手动更改了排序、筛选...等条件后需要重新处理数据时可能会用到 */ updateData () { const { scrollXLoad, scrollYLoad } = this return this.handleTableData(true).then(() => { this.updateFooter() this.checkSelectionStatus() if (scrollXLoad || scrollYLoad) { if (scrollXLoad) { this.updateScrollXSpace() } if (scrollYLoad) { this.updateScrollYSpace() } return this.refreshScroll() } }).then(() => { this.updateCellAreas() return this.recalculate(true) }).then(() => { // 存在滚动行为未结束情况 setTimeout(() => this.recalculate(), 50) }) }, handleTableData (force) { const { scrollYLoad, scrollYStore, fullDataRowIdData, afterFullData } = this let fullList = afterFullData // 是否进行数据处理 if (force) { // 更新数据,处理筛选和排序 this.updateAfterFullData() // 如果为虚拟树,将树结构拍平 fullList = this.handleVirtualTreeToList() } const tableData = scrollYLoad ? fullList.slice(scrollYStore.startIndex, scrollYStore.endIndex) : fullList.slice(0) tableData.forEach((row, $index) => { const rowid = getRowid(this, row) const rest = fullDataRowIdData[rowid] if (rest) { rest.$index = $index } }) this.tableData = tableData return this.$nextTick() }, updateScrollYStatus (fullData) { const { treeConfig, treeOpts, sYOpts } = this const { transform } = treeOpts const allList = fullData || this.tableFullData // 如果gt为0,则总是启用 const scrollYLoad = (transform || !treeConfig) && !!sYOpts.enabled && sYOpts.gt > -1 && (sYOpts.gt === 0 || sYOpts.gt <= allList.length) this.scrollYLoad = scrollYLoad return scrollYLoad }, /** * 加载表格数据 * @param {Array} datas 数据 */ loadTableData (datas) { const { keepSource, treeConfig, treeOpts, editStore, scrollYStore, scrollXStore, lastScrollLeft, lastScrollTop, scrollYLoad: oldScrollYLoad, sXOpts, sYOpts } = this let treeData = [] let fullData = datas ? datas.slice(0) : [] if (treeConfig) { // 树结构自动转换 if (treeOpts.transform) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (!treeOpts.rowField) { errLog('vxe.error.reqProp', ['table.tree-config.rowField']) } if (!treeOpts.parentField) { errLog('vxe.error.reqProp', ['table.tree-config.parentField']) } if (!treeOpts.children) { errLog('vxe.error.reqProp', ['tree-config.children']) } if (!treeOpts.mapChildren) { errLog('vxe.error.reqProp', ['tree-config.mapChildren']) } if (treeOpts.children === treeOpts.mapChildren) { errLog('vxe.error.errConflicts', ['tree-config.children', 'tree-config.mapChildren']) } 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: treeOpts.children, mapChildren: treeOpts.mapChildren }) fullData = treeData.slice(0) } else { treeData = fullData.slice(0) } } scrollYStore.startIndex = 0 scrollYStore.endIndex = 1 scrollXStore.startIndex = 0 scrollXStore.endIndex = 1 editStore.insertList = [] editStore.insertMaps = {} editStore.removeList = [] editStore.removeMaps = {} const sYLoad = this.updateScrollYStatus(fullData) this.scrollYLoad = sYLoad // 全量数据 this.tableFullData = fullData this.tableFullTreeData = treeData // 缓存数据 this.cacheRowMap(true) // 原始数据 this.tableSynchData = datas // 克隆原数据,用于显示编辑状态,与编辑值做对比 if (keepSource) { this.tableSourceData = XEUtils.clone(fullData, true) } if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (sYLoad) { if (!(this.height || this.maxHeight)) { errLog('vxe.error.reqProp', ['table.height | table.max-height | table.scroll-y={enabled: false}']) } if (!this.showOverflow) { warnLog('vxe.error.reqProp', ['table.show-overflow']) } if (this.spanMethod) { warnLog('vxe.error.scrollErrProp', ['table.span-method']) } } } if (this.clearCellAreas && this.mouseConfig) { this.clearCellAreas() this.clearCopyCellArea() } this.clearMergeCells() this.clearMergeFooterItems() this.handleTableData(true) this.updateFooter() return this.$nextTick().then(() => { this.updateHeight() this.updateStyle() }).then(() => { this.computeScrollLoad() }).then(() => { // 是否启用了虚拟滚动 if (sYLoad) { scrollYStore.endIndex = scrollYStore.visibleSize } this.handleReserveStatus() this.checkSelectionStatus() return new Promise(resolve => { this.$nextTick() .then(() => this.recalculate()) .then(() => { let targetScrollLeft = lastScrollLeft let targetScrollTop = lastScrollTop // 是否在更新数据之后自动滚动重置滚动条 if (sXOpts.scrollToLeftOnChange) { targetScrollLeft = 0 } if (sYOpts.scrollToTopOnChange) { targetScrollTop = 0 } // 是否变更虚拟滚动 if (oldScrollYLoad === sYLoad) { restoreScrollLocation(this, targetScrollLeft, targetScrollTop).then(resolve) } else { setTimeout(() => restoreScrollLocation(this, targetScrollLeft, targetScrollTop).then(resolve)) } }) }) }) }, /** * 重新加载数据,不会清空表格状态 * @param {Array} datas 数据 */ loadData (datas) { const { inited, initStatus } = this return this.loadTableData(datas).then(() => { this.inited = true this.initStatus = true if (!initStatus) { this.handleLoadDefaults() } if (!inited) { this.handleInitDefaults() } return this.recalculate() }) }, /** * 重新加载数据,会清空表格状态 * @param {Array} datas 数据 */ reloadData (datas) { const { inited } = this return this.clearAll() .then(() => { this.inited = true this.initStatus = true return this.loadTableData(datas) }) .then(() => { this.handleLoadDefaults() if (!inited) { this.handleInitDefaults() } return this.recalculate() }) }, /** * 局部加载行数据并恢复到初始状态 * 对于行数据需要局部更改的场景中可能会用到 * @param {Row} row 行对象 * @param {Object} record 新数据 * @param {String} field 字段名 */ reloadRow (row, record, field) { const { keepSource, tableSourceData, tableData } = this if (keepSource) { const rowIndex = this.getRowIndex(row) const oRow = tableSourceData[rowIndex] if (oRow && row) { if (field) { const newValue = XEUtils.get(record || row, field) XEUtils.set(row, field, newValue) XEUtils.set(oRow, field, newValue) } else { const newRecord = XEUtils.clone({ ...record }, true) XEUtils.destructuring(oRow, Object.assign(row, newRecord)) } } this.tableData = tableData.slice(0) } else { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.reqProp', ['keep-source']) } } return this.$nextTick() }, /** * 加载列配置 * 对于表格列需要重载、局部递增场景下可能会用到 * @param {ColumnInfo} columns 列配置 */ loadColumn (columns) { const collectColumn = XEUtils.mapTree(columns, column => Cell.createColumn(this, column), { children: 'children' }) return this.handleColumn(collectColumn) }, /** * 加载列配置并恢复到初始状态 * 对于表格列需要重载、局部递增场景下可能会用到 * @param {ColumnInfo} columns 列配置 */ reloadColumn (columns) { return this.clearAll().then(() => { return this.loadColumn(columns) }) }, handleColumn (collectColumn) { this.collectColumn = collectColumn const tableFullColumn = getColumnList(collectColumn) this.tableFullColumn = tableFullColumn this.cacheColumnMap() this.restoreCustomStorage() this.parseColumns().then(() => { if (this.scrollXLoad) { this.loadScrollXData(true) } }) this.clearMergeCells() this.clearMergeFooterItems() this.handleTableData(true) if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if ((this.scrollXLoad || this.scrollYLoad) && this.expandColumn) { warnLog('vxe.error.scrollErrProp', ['column.type=expand']) } } return this.$nextTick().then(() => { if (this.$toolbar) { this.$toolbar.syncUpdate({ collectColumn, $table: this }) } return this.recalculate() }) }, /** * 更新数据行的 Map * 牺牲数据组装的耗时,用来换取使用过程中的流畅 */ cacheRowMap (source) { const { treeConfig, treeOpts, tableFullData, fullDataRowMap, fullAllDataRowMap, tableFullTreeData } = this let { fullDataRowIdData, fullAllDataRowIdData } = this const rowkey = getRowkey(this) const isLazy = treeConfig && treeOpts.lazy const handleCache = (row, index, items, path, parent, nodes) => { let rowid = getRowid(this, row) const seq = treeConfig && path ? toTreePathSeq(path) : index + 1 const level = nodes ? nodes.length - 1 : 0 if (eqEmptyValue(rowid)) { rowid = getRowUniqueId() XEUtils.set(row, rowkey, rowid) } if (isLazy && row[treeOpts.hasChild] && XEUtils.isUndefined(row[treeOpts.children])) { row[treeOpts.children] = null } const rest = { row, rowid, seq, index: treeConfig && parent ? -1 : index, _index: -1, $index: -1, items, parent, level } if (source) { fullDataRowIdData[rowid] = rest fullDataRowMap.set(row, rest) } fullAllDataRowIdData[rowid] = rest fullAllDataRowMap.set(row, rest) } if (source) { fullDataRowIdData = this.fullDataRowIdData = {} fullDataRowMap.clear() } fullAllDataRowIdData = this.fullAllDataRowIdData = {} fullAllDataRowMap.clear() if (treeConfig) { XEUtils.eachTree(tableFullTreeData, handleCache, treeOpts) } else { tableFullData.forEach(handleCache) } }, loadTreeChildren (row, childRecords) { const { keepSource, tableSourceData, treeOpts, fullDataRowIdData, fullDataRowMap, fullAllDataRowMap, fullAllDataRowIdData } = this const { transform, children, mapChildren } = treeOpts const rest = fullAllDataRowIdData[getRowid(this, row)] const parentLevel = rest ? rest.level : 0 return this.createData(childRecords).then((rows) => { if (keepSource) { const rowid = getRowid(this, row) const matchObj = XEUtils.findTree(tableSourceData, (item) => rowid === getRowid(this, item), treeOpts) if (matchObj) { matchObj.item[children] = XEUtils.clone(rows, true) } } XEUtils.eachTree(rows, (childRow, index, items, path, parent, nodes) => { const rowid = getRowid(this, childRow) const rest = { row: childRow, rowid, seq: -1, index, _index: -1, $index: -1, items, parent, level: parentLevel + nodes.length } fullDataRowIdData[rowid] = rest fullDataRowMap.set(childRow, rest) fullAllDataRowIdData[rowid] = rest fullAllDataRowMap.set(childRow, rest) }, treeOpts) row[children] = rows if (transform) { row[mapChildren] = rows } this.updateAfterDataIndex() return rows }) }, /** * 更新数据列的 Map * 牺牲数据组装的耗时,用来换取使用过程中的流畅 */ cacheColumnMap () { const { tableFullColumn, collectColumn, fullColumnMap, showOverflow, columnOpts, rowOpts } = this const fullColumnIdData = this.fullColumnIdData = {} const fullColumnFieldData = this.fullColumnFieldData = {} const isGroup = collectColumn.some(hasChildrenList) let isAllOverflow = !!showOverflow let expandColumn let treeNodeColumn let checkboxColumn let radioColumn let htmlColumn let hasFixed const handleFunc = (column, index, items, path, parent) => { const { id: colid, field, fixed, type, treeNode } = column const rest = { column, colid, index, items, parent } if (field) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (fullColumnFieldData[field]) { warnLog('vxe.error.colRepet', ['field', field]) } } fullColumnFieldData[field] = rest } if (!hasFixed && fixed) { hasFixed = fixed } if (!htmlColumn && type === 'html') { htmlColumn = column } if (treeNode) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (treeNodeColumn) { warnLog('vxe.error.colRepet', ['tree-node', treeNode]) } } if (!treeNodeColumn) { treeNodeColumn = column } } else if (type === 'expand') { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (expandColumn) { warnLog('vxe.error.colRepet', ['type', type]) } } if (!expandColumn) { expandColumn = column } } if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { 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 (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (this.showOverflow && column.showOverflow === false) { warnLog('vxe.error.errConflicts', [`table.show-overflow=${this.showOverflow}`, `column.show-overflow=${column.showOverflow}`]) } if (this.showHeaderOverflow && column.showHeaderOverflow === false) { warnLog('vxe.error.errConflicts', [`table.show-header-overflow=${this.showHeaderOverflow}`, `column.show-header-overflow=${column.showHeaderOverflow}`]) } if (this.showFooterOverflow && column.showFooterOverflow === false) { warnLog('vxe.error.errConflicts', [`table.show-footer-overflow=${this.showFooterOverflow}`, `column.show-footer-overflow=${column.showFooterOverflow}`]) } } if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (htmlColumn) { if (!columnOpts.useKey) { errLog('vxe.error.reqProp', ['column-config.useKey', 'column.type=html']) } if (!rowOpts.useKey) { errLog('vxe.error.reqProp', ['row-config.useKey', 'column.type=html']) } } } if (isAllOverflow && column.showOverflow === false) { isAllOverflow = false } if (fullColumnIdData[colid]) { errLog('vxe.error.colRepet', ['colId', colid]) } fullColumnIdData[colid] = rest fullColumnMap.set(column, rest) } fullColumnMap.clear() if (isGroup) { XEUtils.eachTree(collectColumn, (column, index, items, path, parent, nodes) => { column.level = nodes.length handleFunc(column, index, items, path, parent) }) } else { tableFullColumn.forEach(handleFunc) } if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (expandColumn && this.mouseOpts.area) { errLog('vxe.error.errConflicts', ['mouse-config.area', 'column.type=expand']) } } this.isGroup = isGroup this.treeNodeColumn = treeNodeColumn this.expandColumn = expandColumn this.isAllOverflow = isAllOverflow }, /** * 根据 tr 元素获取对应的 row 信息 * @param {Element} tr 元素 */ getRowNode (tr) { if (tr) { const { fullAllDataRowIdData } = this const rowid = tr.getAttribute('rowid') const rest = fullAllDataRowIdData[rowid] if (rest) { return { rowid: rest.rowid, item: rest.row, index: rest.index, items: rest.items, parent: rest.parent } } } return null }, /** * 根据 th/td 元素获取对应的 column 信息 * @param {Element} cell 元素 */ getColumnNode (cell) { if (cell) { const { fullColumnIdData } = this const colid = cell.getAttribute('colid') const rest = fullColumnIdData[colid] if (rest) { return { colid: rest.colid, item: rest.column, index: rest.index, items: rest.items, parent: rest.parent } } } return null }, /** * 根据 row 获取序号 * @param {Row} row 行对象 */ getRowSeq (row) { const { fullDataRowIdData } = this if (row) { const rowid = getRowid(this, row) const rest = fullDataRowIdData[rowid] if (rest) { return rest.seq } } return -1 }, /** * 根据 row 获取相对于 data 中的索引 * @param {Row} row 行对象 */ getRowIndex (row) { return this.fullDataRowMap.has(row) ? this.fullDataRowMap.get(row).index : -1 }, /** * 根据 row 获取相对于当前数据中的索引 * @param {Row} row 行对象 */ getVTRowIndex (row) { return this.afterFullData.indexOf(row) }, // 在 v3 中废弃 _getRowIndex (row) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.delFunc', ['_getRowIndex', 'getVTRowIndex']) } return this.getVTRowIndex(row) }, /** * 根据 row 获取渲染中的虚拟索引 * @param {Row} row 行对象 */ getVMRowIndex (row) { return this.tableData.indexOf(row) }, // 在 v3 中废弃 $getRowIndex (row) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.delFunc', ['$getRowIndex', 'getVMRowIndex']) } return this.getVMRowIndex(row) }, /** * 根据 column 获取相对于 columns 中的索引 * @param {ColumnInfo} column 列配置 */ getColumnIndex (column) { return this.fullColumnMap.has(column) ? this.fullColumnMap.get(column).index : -1 }, /** * 根据 column 获取相对于当前表格列中的索引 * @param {ColumnInfo} column 列配置 */ getVTColumnIndex (column) { return this.visibleColumn.indexOf(column) }, // 在 v3 中废弃 _getColumnIndex (column) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.delFunc', ['_getColumnIndex', 'getVTColumnIndex']) } return this.getVTColumnIndex(column) }, /** * 根据 column 获取渲染中的虚拟索引 * @param {ColumnInfo} column 列配置 */ getVMColumnIndex (column) { return this.tableColumn.indexOf(column) }, // 在 v3 中废弃 $getColumnIndex (column) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.delFunc', ['$getColumnIndex', 'getVMColumnIndex']) } return this.getVMColumnIndex(column) }, /** * 判断是否为索引列 * @param {ColumnInfo} column 列配置 */ isSeqColumn (column) { return column && column.type === 'seq' }, /** * 定义行数据中的列属性,如果不存在则定义 * @param {Row} records 行数据 */ defineField (records) { const { radioOpts, checkboxOpts, treeConfig, treeOpts, expandOpts } = this const rowkey = getRowkey(this) if (!XEUtils.isArray(records)) { records = [records || {}] } return records.map(record => { this.tableFullColumn.forEach(column => { const { field, editRender } = column if (field && !XEUtils.has(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 = [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[treeOpts.children])) { record[treeOpts.children] = null } // 必须有行数据的唯一主键,可以自行设置;也可以默认生成一个随机数 if (eqEmptyValue(XEUtils.get(record, rowkey))) { XEUtils.set(record, rowkey, getRowUniqueId()) } return record }) }, /** * 创建 data 对象 * 对于某些特殊场景可能会用到,会自动对数据的字段名进行检测,如果不存在就自动定义 * @param {Array} records 新数据 */ createData (records) { return this.$nextTick().then(() => { return this.defineField(records) }) }, /** * 创建 Row|Rows 对象 * 对于某些特殊场景需要对数据进行手动插入时可能会用到 * @param {Array/Object} records 新数据 */ createRow (records) { const isArr = XEUtils.isArray(records) if (!isArr) { records = [records] } return this.createData(records).then(rows => isArr ? rows : rows[0]) }, /** * 还原数据 * 如果不传任何参数,则还原整个表格 * 如果传 row 则还原一行 * 如果传 rows 则还原多行 * 如果还额外传了 field 则还原指定的单元格数据 */ revertData (rows, field) { const { keepSource, tableSourceData, treeConfig } = this if (!keepSource) { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { warnLog('vxe.error.reqProp', ['keep-source']) } return this.$nextTick() } let targetRows = rows if (rows) { if (!XEUtils.isArray(rows)) { targetRows = [rows] } } else { targetRows = XEUtils.toArray(this.getUpdateRecords()) } if (targetRows.length) { targetRows.forEach(row => { if (!this.isInsertByRow(row)) { const rowIndex = this.getRowIndex(row) if (treeConfig && rowIndex === -1) { errLog('vxe.error.noTree', ['revertData']) } const oRow = tableSourceData[rowIndex] 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 (rows) { return this.$nextTick() } return this.reloadData(tableSourceData) }, /** * 清空单元格内容 * 如果不创参数,则清空整个表格内容 * 如果传 row 则清空一行内容 * 如果传 rows 则清空多行内容 * 如果还额外传了 field 则清空指定单元格内容 * @param {Array/Row} rows 行数据 * @param {String} field 字段名 */ clearData (rows, field) { const { tableFullData, visibleColumn } = this if (!arguments.length) { rows = tableFullData } else if (rows && !XEUtils.isArray(rows)) { rows = [rows] } if (field) { rows.forEach(row => XEUtils.set(row, field, null)) } else { rows.forEach(row => { visibleColumn.forEach(column => { if (column.field) { setCellValue(row, column, null) } }) }) } return this.$nextTick() }, /** * 检查是否为临时行数据 * @param {Row} row 行对象 */ isInsertByRow (row) { const { editStore } = this const rowid = getRowid(this, row) return editStore.insertList.length && editStore.insertMaps[rowid] }, /** * 删除所有新增的临时数据 * @returns */ removeInsertRow () { return this.remove(this.editStore.insertList) }, /** * 检查行或列数据是否发生改变 * @param {Row} row 行对象 * @param {String} field 字段名 */ isUpdateByRow (row, field) { const { visibleColumn, keepSource, treeConfig, treeOpts, tableSourceData, fullDataRowIdData } = this if (keepSource) { let oRow, property const rowid = getRowid(this, row) // 新增的数据不需要检测 if (!fullDataRowIdData[rowid]) { return false } if (treeConfig) { const children = treeOpts.children const matchObj = XEUtils.findTree(tableSourceData, item => rowid === getRowid(this, item), treeOpts) row = Object.assign({}, row, { [children]: null }) if (matchObj) { oRow = Object.assign({}, matchObj.item, { [children]: null }) } } else { const oRowIndex = fullDataRowIdData[rowid].index oRow = tableSourceData[oRowIndex] } if (oRow) { if (arguments.length > 1) { return !eqCellValue(oRow, row, field) } for (let index = 0, len = visibleColumn.length; index < len; index++) { property = visibleColumn[index].field if (property && !eqCellValue(oRow, row, property)) { return true } } } } return false }, /** * 获取表格的可视列,也可以指定索引获取列 * @param {Number} columnIndex 索引 */ getColumns (columnIndex) { const columns = this.visibleColumn return XEUtils.isUndefined(columnIndex) ? columns.slice(0) : columns[columnIndex] }, /** * 根据列的唯一主键获取列 * @param {String} colid 列主键 */ getColumnById (colid) { const fullColumnIdData = this.fullColumnIdData return fullColumnIdData[colid] ? fullColumnIdData[colid].column : null }, /** * 根据列的字段名获取列 * @param {String} field 字段名 */ getColumnByField (field) { const fullColumnFieldData = this.fullColumnFieldData return fullColumnFieldData[field] ? fullColumnFieldData[field].column : null }, /** * 获取当前表格的列 * 收集到的全量列、全量表头列、处理条件之后的全量表头列、当前渲染中的表头列 */ getTableColumn () { return { collectColumn: this.collectColumn.slice(0), fullColumn: this.tableFullColumn.slice(0), visibleColumn: this.visibleColumn.slice(0), tableColumn: this.tableColumn.slice(0) } }, /** * 获取数据,和 data 的行为一致,也可以指定索引获取数据 */ getData (rowIndex) { const tableSynchData = this.data || this.tableSynchData return XEUtils.isUndefined(rowIndex) ? tableSynchData.slice(0) : tableSynchData[rowIndex] }, /** * 用于多选行,获取已选中的数据 */ getCheckboxRecords (isFull) { const { tableFullData, afterFullData, treeConfig, treeOpts, checkboxOpts, tableFullTreeData, afterTreeFullData } = this const { transform, children, mapChildren } = treeOpts const { checkField } = checkboxOpts const currTableData = isFull ? (transform ? tableFullTreeData : tableFullData) : (transform ? afterTreeFullData : afterFullData) let rowList = [] if (checkField) { if (treeConfig) { rowList = XEUtils.filterTree(currTableData, row => XEUtils.get(row, checkField), { children: transform ? mapChildren : children }) } else { rowList = currTableData.filter(row => XEUtils.get(row, checkField)) } } else { const { selection } = this if (treeConfig) { rowList = XEUtils.filterTree(currTableData, row => this.findRowIndexOf(selection, row) > -1, { children: transform ? mapChildren : children }) } else { rowList = currTableData.filter(row => this.findRowIndexOf(selection, row) > -1) } } return rowList }, /** * 如果为虚拟树,将树结构拍平 * @returns */ handleVirtualTreeToList () { const { treeOpts, treeConfig, treeExpandeds, afterTreeFullData, afterFullData } = this if (treeConfig && treeOpts.transform) { const fullData = [] const expandMaps = new Map() XEUtils.eachTree(afterTreeFullData, (row, index, items, path, parent) => { if (!parent || (expandMaps.has(parent) && treeExpandeds.indexOf(parent) > -1)) { expandMaps.set(row, 1) fullData.push(row) } }, { children: treeOpts.mapChildren }) this.afterFullData = fullData this.updateScrollYStatus(fullData) return fullData } return afterFullData }, /** * 获取处理后全量的表格数据 * 如果存在筛选条件,继续处理 */ updateAfterFullData () { const { tableFullColumn, tableFullData, filterOpts, sortOpts, treeConfig, treeOpts, tableFullTreeData } = this const { remote: allRemoteFilter, filterMethod: allFilterMethod } = filterOpts const { remote: allRemoteSort, sortMethod: allSortMethod, multiple: sortMultiple, chronological } = sortOpts const { transform } = treeOpts let tableData = [] let tableTree = [] const filterColumns = [] let orderColumns = [] tableFullColumn.forEach(column => { const { field, sortable, order, filters } = column if (!allRemoteFilter && filters && filters.length) { const valueList = [] const itemList = [] filters.forEach((item) => { if (item.checked) { itemList.push(item) valueList.push(item.value) } }) if (itemList.length) { filterColumns.push({ column, valueList, itemList }) } } if (!allRemoteSort && sortable && order) { orderColumns.push({ column, field, property: field, order, sortTime: column.sortTime }) } }) if (sortMultiple && chronological && orderColumns.length > 1) { orderColumns = XEUtils.orderBy(orderColumns, 'sortTime') } if (filterColumns.length) { const handleFilter = (row) => { return filterColumns.every(({ column, valueList, itemList }) => { if (valueList.length && !allRemoteFilter) { const { filterMethod, filterRender, field } = column const compConf = filterRender ? VXETable.renderer.get(filterRender.name) : null const compFilterMethod = compConf && compConf.renderFilter ? compConf.filterMethod : null const defaultFilterMethod = compConf ? compConf.defaultFilterMethod : null const cellValue = UtilTools.getCellValue(row, column) if (filterMethod) { return itemList.some((item) => filterMethod({ value: item.value, option: item, cellValue, row, column, $table: this })) } else if (compFilterMethod) { return itemList.some((item) => compFilterMethod({ value: item.value, option: item, cellValue, row, column, $table: this })) } else if (allFilterMethod) { return allFilterMethod({ options: itemList, values: valueList, cellValue, row, column }) } else if (defaultFilterMethod) { return itemList.some((item) => defaultFilterMethod({ value: item.value, option: item, cellValue, row, column, $table: this })) } return valueList.indexOf(XEUtils.get(row, field)) > -1 } return true }) } if (treeConfig && transform) { // 筛选虚拟树 tableTree = XEUtils.searchTree(tableFullTreeData, handleFilter, { ...treeOpts, original: true }) tableData = tableTree } else { tableData = treeConfig ? tableFullTreeData.filter(handleFilter) : tableFullData.filter(handleFilter) tableTree = tableData } } else { if (treeConfig && transform) { // 还原虚拟树 tableTree = XEUtils.searchTree(tableFullTreeData, () => true, { ...treeOpts, original: true }) tableData = tableTree } else { tableData = treeConfig ? tableFullTreeData.slice(0) : tableFullData.slice(0) tableTree = tableData } } const firstOrderColumn = orderColumns[0] if (!allRemoteSort && firstOrderColumn) { if (treeConfig && transform) { // 虚拟树和列表一样,只能排序根级节点 if (allSortMethod) { const sortRests = allSortMethod({ data: tableTree, sortList: orderColumns, $table: this }) tableTree = XEUtils.isArray(sortRests) ? sortRests : tableTree } else { tableTree = XEUtils.orderBy(tableTree, orderColumns.map(({ column, order }) => [getOrderField(this, column), order])) } tableData = tableTree } else { if (allSortMethod) { const sortRests = allSortMethod({ data: tableData, column: firstOrderColumn.column, property: firstOrderColumn.field, field: firstOrderColumn.field, order: firstOrderColumn.order, sortList: orderColumns, $table: this }) tableData = XEUtils.isArray(sortRests) ? sortRests : tableData } else { // 兼容 v4 if (sortMultiple) { tableData = XEUtils.orderBy(tableData, orderColumns.map(({ column, order }) => [getOrderField(this, column), order])) } else { // 兼容 v2,在 v4 中废弃, sortBy 不能为数组 let sortByConfs if (XEUtils.isArray(firstOrderColumn.sortBy)) { sortByConfs = firstOrderColumn.sortBy.map(item => [item, firstOrderColumn.order]) } tableData = XEUtils.orderBy(tableData, sortByConfs || [firstOrderColumn].map(({ column, order }) => [getOrderField(this, column), order])) } } tableTree = tableData } } this.afterFullData = tableData this.afterTreeFullData = tableTree this.updateAfterDataIndex() }, /** * 预编译 * 对渲染中的数据提前解析序号及索引。牺牲提前编译耗时换取渲染中额外损耗,使运行时更加流畅 */ updateAfterDataIndex () { const { treeConfig, afterFullData, fullDataRowIdData, fullAllDataRowIdData, afterTreeFullData, treeOpts } = this if (treeConfig) { XEUtils.eachTree(afterTreeFullData, (row, index, items, path) => { const rowid = getRowid(this, row) const allrest = fullAllDataRowIdData[rowid] const seq = path.map((num, i) => i % 2 === 0 ? (Number(num) + 1) : '.').join('') if (allrest) { allrest.seq = seq allrest._index = index } else { const rest = { row, rowid, seq, index: -1, $index: -1, _index: index, items: [], parent: null, level: 0 } fullAllDataRowIdData[rowid] = rest fullDataRowIdData[rowid] = rest } }, { children: treeOpts.transform ? treeOpts.mapChildren : treeOpts.children }) } else { afterFullData.forEach((row, index) => { const rowid = getRowid(this, row) const allrest = fullAllDataRowIdData[rowid] const seq = index + 1 if (allrest) { allrest.seq = seq allrest._index = index } else { const rest = { row, rowid, seq, index: -1, $index: -1, _index: index, items: [], parent: null, level: 0 } fullAllDataRowIdData[rowid] = rest fullDataRowIdData[rowid] = rest } }) } }, /** * 只对 tree-config 有效,获取行的父级 */ getParentRow (rowOrRowid) { const { treeConfig, fullDataRowIdData } = this if (rowOrRowid && treeConfig) { let rowid if (XEUtils.isString(rowOrRowid)) { rowid = rowOrRowid } else { rowid = getRowid(this, rowOrRowid) } if (rowid) { return fullDataRowIdData[rowid] ? fullDataRowIdData[rowid].parent : null } } return null }, /** * 根据行的唯一主键获取行 * @param {String/Number} rowid 行主键 */ getRowById (cellValue) { const fullDataRowIdData = this.fullDataRowIdData const rowid = XEUtils.eqNull(cellValue) ? '' : encodeURIComponent(cellValue) return fullDataRowIdData[rowid] ? fullDataRowIdData[rowid].row : null }, /** * 根据行获取行的唯一主键 * @param {Row} row 行对象 */ getRowid (row) { const fullAllDataRowMap = this.fullAllDataRowMap return fullAllDataRowMap.has(row) ? fullAllDataRowMap.get(row).rowid : null }, /** * 获取处理后的表格数据 * 如果存在筛选条件,继续处理 * 如果存在排序,继续处理 */ getTableData () { const { tableFullData, afterFullData, tableData, footerTableData } = this return { fullData: tableFullData.slice(0), visibleData: afterFullData.slice(0), tableData: tableData.slice(0), footerData: footerTableData.slice(0) } }, /** * 处理数据加载默认行为 * 默认执行一次,除非被重置 */ handleLoadDefaults () { if (this.checkboxConfig) { this.handleDefaultSelectionChecked() } if (this.radioConfig) { this.handleDefaultRadioChecked() } if (this.expandConfig) { this.handleDefaultRowExpand() } if (this.treeConfig) { this.handleDefaultTreeExpand() } if (this.mergeCells) { this.handleDefaultMergeCells() } if (this.mergeFooterItems) { this.handleDefaultMergeFooterItems() } this.$nextTick(() => setTimeout(this.recalculate)) }, /** * 处理初始化的默认行为 * 只会执行一次 */ handleInitDefaults () { const { sortConfig } = this if (sortConfig) { this.handleDefaultSort() } }, /** * 设置为固定列 */ setColumnFixed (fieldOrColumn, fixed) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column && column.fixed !== fixed) { XEUtils.eachTree([column], (column) => { column.fixed = fixed }) this.saveCustomFixed() return this.refreshColumn() } return this.$nextTick() }, /** * 取消指定固定列 */ clearColumnFixed (fieldOrColumn) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column && column.fixed) { XEUtils.eachTree([column], (column) => { column.fixed = null }) this.saveCustomFixed() return this.refreshColumn() } return this.$nextTick() }, /** * 隐藏指定列 */ hideColumn (fieldOrColumn) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column && column.visible) { column.visible = false return this.handleCustom() } return this.$nextTick() }, /** * 显示指定列 */ showColumn (fieldOrColumn) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column & !column.visible) { column.visible = true return this.handleCustom() } return this.$nextTick() }, setColumnWidth (fieldOrColumn, width) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column) { const colWidth = XEUtils.toInteger(width) let rdWidth = colWidth if (DomTools.isScale(width)) { const { tableBody } = this.$refs const tableBodyElem = tableBody ? tableBody.$el : null const bodyWidth = tableBodyElem ? tableBodyElem.clientWidth - 1 : 0 rdWidth = Math.floor(colWidth * bodyWidth) } column.renderWidth = rdWidth } return this.$nextTick() }, getColumnWidth (fieldOrColumn) { const column = handleFieldOrColumn(this, fieldOrColumn) if (column) { return column.renderWidth } return 0 }, /** * 手动重置列的显示隐藏、列宽拖动的状态; * 如果为 true 则重置所有状态 * 如果已关联工具栏,则会同步更新 */ resetColumn (options) { const { customOpts } = this const { checkMethod } = customOpts const opts = Object.assign({ visible: true, resizable: options === true }, options) this.tableFullColumn.forEach(column => { if (opts.resizable) { column.resizeWidth = 0 } if (!checkMethod || checkMethod({ column })) { column.visible = column.defaultVisible } }) if (opts.resizable) { this.saveCustomResizable(true) } return this.handleCustom() }, handleCustom () { this.saveCustomVisible() this.analyColumnWidth() return this.refreshColumn() }, /** * 还原自定义列操作状态 */ restoreCustomStorage () { const { id, collectColumn, customConfig, customOpts } = this const { storage } = customOpts const isAllStorage = customOpts.storage === true const isCustomResizable = isAllStorage || (storage && storage.resizable) const isCustomVisible = isAllStorage || (storage && storage.visible) const isCustomFixed = storage === true || (storage && storage.fixed) const isCustomOrder = storage === true || (storage && storage.order) if (customConfig && (isCustomResizable || isCustomVisible || isCustomFixed || isCustomOrder)) { const customMap = {} if (!id) { errLog('vxe.error.reqProp', ['id']) return } if (isCustomResizable) { const columnWidthStorage = getCustomStorageMap(resizableStorageKey)[id] if (columnWidthStorage) { XEUtils.each(columnWidthStorage, (resizeWidth, field) => { customMap[field] = { field, resizeWidth } }) } } // 自定义固定列 if (isCustomFixed) { const columnFixedStorage = getCustomStorageMap(fixedStorageKey)[id] if (columnFixedStorage) { const colFixeds = columnFixedStorage.split(',') colFixeds.forEach((fixConf) => { const [field, fixed] = fixConf.split('|') if (customMap[field]) { customMap[field].fixed = fixed } else { customMap[field] = { field, fixed } } }) } } // 自定义顺序 if (isCustomOrder) { const columnOrderStorage = getCustomStorageMap(orderStorageKey)[id] if (columnOrderStorage) { // 开发中... } } if (isCustomVisible) { const columnVisibleS