UNPKG

e-virt-table

Version:

A powerful data table based on canvas. You can use it as data grid、Microsoft Excel or Google sheets. It supports virtual scroll、cell edit etc.

1,483 lines 70.8 kB
import Validator from './Validator'; import { generateShortUUID, toLeaf, compareDates } from './util'; import Cell from './Cell'; export default class Database { constructor(ctx, options) { Object.defineProperty(this, "ctx", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "data", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "columns", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "footerData", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "rowKeyMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "colIndexKeyMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "headerMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "rowIndexRowKeyMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "rowKeyRowIndexMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "checkboxKeyMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "selectionMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "expandMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "originalDataMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "changedDataMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "validationErrorMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "itemRowKeyMap", { enumerable: true, configurable: true, writable: true, value: new WeakMap() }); Object.defineProperty(this, "bufferData", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "customHeader", { enumerable: true, configurable: true, writable: true, value: { fixedData: {}, sortData: {}, hideData: {}, resizableData: {}, } }); Object.defineProperty(this, "overlayerAutoHeightMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "maxRowHeightMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); // 记录每行的最大渲染高度(按 rowKey 存储) Object.defineProperty(this, "bufferCheckState", { enumerable: true, configurable: true, writable: true, value: { buffer: false, check: false, indeterminate: false, selectable: true, } }); Object.defineProperty(this, "sumHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "filterMethod", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "positions", { enumerable: true, configurable: true, writable: true, value: [] }); //虚拟滚动位置 Object.defineProperty(this, "sortState", { enumerable: true, configurable: true, writable: true, value: new Map() }); this.ctx = ctx; const { data = [], columns = [], footerData = [] } = options; this.data = data; this.footerData = footerData; this.columns = columns; this.init(); } // 初始化默认不忽略清空改变值和校验map init(isClear = true) { this.ctx.paint.clearTextCache(); this.clearBufferData(); this.rowKeyMap.clear(); this.checkboxKeyMap.clear(); this.colIndexKeyMap.clear(); this.rowIndexRowKeyMap.clear(); this.rowKeyRowIndexMap.clear(); // 清空最大行高记录 this.maxRowHeightMap.clear(); // 判断是否有选择和树形结构 const _columns = this.getColumns(); const leafColumns = toLeaf(_columns); this.ctx.hasSelection = leafColumns.some((item) => item.type === 'selection'); this.ctx.hasTree = leafColumns.some((item) => item.type === 'tree'); if (isClear) { this.originalDataMap.clear(); this.changedDataMap.clear(); this.validationErrorMap.clear(); const { ROW_KEY } = this.ctx.config; // 清除选中和展开状态,如果没有ROW_KEY清除 if (!ROW_KEY) { // 无行主键时直接清除所有状态 this.selectionMap.clear(); this.expandMap.clear(); } else { // 有行主键,根据上下文条件清除部分状态 if (!this.ctx.hasSelection) { this.selectionMap.clear(); } if (!this.ctx.hasTree) { this.expandMap.clear(); } } } this.itemRowKeyMap = new WeakMap(); this.initData(this.data); this.getData(); this.bufferCheckState.buffer = false; } /** * 清除缓存数据 */ clearBufferData() { this.bufferData = []; } /** * 初始化数据 * @param dataList * @param level */ initData(dataList, level = 0, parentRowKeys = []) { const siblingsLength = dataList.length; const { ROW_KEY = '', DEFAULT_EXPAND_ALL, CELL_HEIGHT, SELECTABLE_METHOD, CHECKBOX_KEY, TREE_CHILDREN_KEY, } = this.ctx.config; dataList.forEach((item, index) => { if (TREE_CHILDREN_KEY !== 'children') { item.children = item[TREE_CHILDREN_KEY]; } const _rowKey = item[ROW_KEY]; // 行唯一标识,否则就rowKey const rowKey = _rowKey !== undefined && _rowKey !== null ? `${_rowKey}` : generateShortUUID(); this.itemRowKeyMap.set(item, rowKey); const height = item._height || CELL_HEIGHT; const readonly = item._readonly; let selectable = true; if (typeof SELECTABLE_METHOD === 'function') { const selectableMethod = SELECTABLE_METHOD; selectable = selectableMethod; } // 存储checkboxKey,处理合并单元格选中 if (CHECKBOX_KEY) { const checkboxKey = item[CHECKBOX_KEY]; if (this.checkboxKeyMap.has(checkboxKey)) { const checkboxKeys = this.checkboxKeyMap.get(checkboxKey) || []; checkboxKeys.push(rowKey); this.checkboxKeyMap.set(checkboxKey, checkboxKeys); } else { this.checkboxKeyMap.set(checkboxKey, [rowKey]); } } // 存key this.selectionMap.set(rowKey, { key: CHECKBOX_KEY ? item[CHECKBOX_KEY] : rowKey, row: item, check: this.selectionMap.get(rowKey)?.check || false, }); const expand = DEFAULT_EXPAND_ALL || this.expandMap.get(rowKey) || item._expand || false; this.expandMap.set(rowKey, expand); this.rowKeyMap.set(rowKey, { readonly, index, rowIndex: index, level, height, calculatedHeight: -1, check: false, selectable, expand, expandLazy: false, hasChildren: item._hasChildren || (Array.isArray(item.children) ? item.children.length > 0 : false), expandLoading: false, item, parentRowKeys, parentRowKey: parentRowKeys[parentRowKeys.length - 1] || '', isLastChild: index === siblingsLength - 1, }); if (Array.isArray(item.children)) { if (item.children.length) { this.initData(item.children, level + 1, [...parentRowKeys, rowKey]); } } }); } /** * * @param rowKey 设置Row高度 * @param height */ setRowHeight(rowIndex, height) { const rowKey = this.rowIndexRowKeyMap.get(rowIndex); if (rowKey === undefined) { return; } const row = this.rowKeyMap.get(rowKey); row.height = height; row.item._height = height; this.clearBufferData(); // 清除缓存数据 } // 批量设置行高度 setBatchRowHeight(rowIndexHeightList) { rowIndexHeightList.forEach(({ rowIndex, height }) => { const rowKey = this.rowIndexRowKeyMap.get(rowIndex); if (rowKey) { const row = this.rowKeyMap.get(rowKey); row.height = height; row.item._height = height; } }); this.clearBufferData(); // 清除缓存数据 } // 批量设置计算行高度 setBatchCalculatedRowHeight(rowIndexHeightList) { // 判断是否需要更新 const isNeedUpdate = rowIndexHeightList.every(({ height, rowIndex }) => { const position = this.getPositionForRowIndex(rowIndex); return position.calculatedHeight === height; }); if (isNeedUpdate) { return; } rowIndexHeightList.forEach(({ rowIndex, height }) => { const rowKey = this.rowIndexRowKeyMap.get(rowIndex); if (rowKey) { const row = this.rowKeyMap.get(rowKey); row.calculatedHeight = height; // 如果开启了记录最大行高功能,更新最大高度记录(使用 rowKey) if (this.ctx.config.REMEMBER_MAX_ROW_HEIGHT) { const maxHeight = this.maxRowHeightMap.get(rowKey) || row.height; if (height > maxHeight) { this.maxRowHeightMap.set(rowKey, height); } } } }); this.clearBufferData(); // 清除缓存数据 this.getData(); // 重新获取数据 this.ctx.emit('draw'); } /** * 清除最大行高记录 */ clearMaxRowHeight() { this.maxRowHeightMap.clear(); this.clearBufferData(); // 清除缓存数据 this.getData(); // 重新获取数据 this.ctx.emit('draw'); } /** * 获取所有行数据(平铺) * @returns 获取转化平铺数据 */ getAllRowsData() { let list = []; const recursiveData = (data) => { data.forEach((item) => { list.push(item); if (Array.isArray(item.children)) { recursiveData(item.children); } }); }; recursiveData(this.data); return list; } generateColumns(columns) { const _generateColumns = (columns) => { return columns.map((column) => { const children = column.children && Array.isArray(column.children) ? _generateColumns(column.children) : undefined; const dataMap = { hide: this.customHeader?.hideData?.[column.key], fixed: this.customHeader?.fixedData?.[column.key], sort: this.customHeader?.sortData?.[column.key], width: this.customHeader?.resizableData?.[column.key], }; const obj = {}; for (const [key, value] of Object.entries(dataMap)) { if (value !== undefined) obj[key] = value; } const allHide = children && children.every((item) => item.hide); // 所有子项都隐藏那父级也要隐藏 return { ...column, children, hide: allHide || (typeof column.hide === 'function' ? column.hide(column) : column.hide), ...obj, }; }); }; return _generateColumns(columns); } getColumns() { const list = this.generateColumns(this.columns); return list; } setColumns(columns) { this.columns = columns; this.clearBufferData(); } setData(data) { this.data = data; this.init(); } /** * 统一转化数据,给画body使用,包括过滤树状等,统一入口 * @returns */ getData() { if (this.bufferData.length > 0) { return { data: this.bufferData, sumHeight: this.sumHeight, positions: this.positions, }; } let list = []; let rowIndex = 0; this.sumHeight = 0; this.positions = []; const recursiveData = (data) => { data.forEach((item) => { list.push(item); const rowKey = this.itemRowKeyMap.get(item); const { expand, hasChildren, height, calculatedHeight } = this.rowKeyMap.get(rowKey); const top = this.sumHeight; // 计算行高度和设置高度取最大 let _height = Math.max(calculatedHeight, height); // 如果开启了记录最大行高功能 if (this.ctx.config.REMEMBER_MAX_ROW_HEIGHT) { // 使用 rowKey 作为键,获取该行历史最大高度 const maxHeight = this.maxRowHeightMap.get(rowKey) || height; // 如果当前计算高度大于历史最大高度,更新最大高度 if (_height > maxHeight) { this.maxRowHeightMap.set(rowKey, _height); _height = _height; } else { // 使用历史最大高度 _height = maxHeight; } } this.sumHeight += _height; this.rowIndexRowKeyMap.set(rowIndex, rowKey); this.rowKeyRowIndexMap.set(rowKey, rowIndex); this.positions.push({ top, height: _height, bottom: this.sumHeight, calculatedHeight: calculatedHeight, }); rowIndex += 1; if (expand && hasChildren) { recursiveData(item.children); } }); }; this.rowIndexRowKeyMap.clear(); this.rowKeyRowIndexMap.clear(); let _data = this.data; if (typeof this.filterMethod === 'function') { _data = this.filterMethod(_data); } // 说明需要排序 if (this.sortState.size) { // 按时间戳排序,最早的先排序 const sortedEntries = Array.from(this.sortState.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp); // 对 sortedData 进行排序 _data = this.sortDataRecursive(_data, sortedEntries); } recursiveData(_data); this.bufferData = list; return { data: list, sumHeight: this.sumHeight, positions: this.positions, }; } setFooterData(data) { this.footerData = data; } getFooterData() { return this.footerData; } /** * 设置过滤方法 */ setFilterMethod(filterMethod) { this.filterMethod = filterMethod; } /** * 清空过滤方法 */ clearFilterMethod() { this.filterMethod = undefined; } /** * 获取排序状态 */ getSortState(key) { return this.sortState.get(key) || { direction: 'none', timestamp: 0 }; } /** * 设置排序状态 */ setSortState(key, direction) { const timestamp = Date.now(); if (this.ctx.config.SORT_STRICTLY) { // 严格排序,清除所有排序状态 this.sortState.clear(); } if (direction === 'none') { // 清除排序状态 this.sortState.delete(key); } else { this.sortState.set(key, { direction, timestamp }); } this.ctx.emit('sortChange', this.sortState); this.clearBufferData(); this.ctx.emit('draw'); } /** * 清除所有排序状态 */ clearSort() { this.sortState.clear(); this.ctx.emit('sortChange', this.sortState); this.clearBufferData(); this.ctx.emit('draw'); } /** * 递归排序方法,支持树形数据 * @param data 要排序的数据 * @param sortedEntries 排序条目,按时间戳排序 * @returns 排序后的数据 */ sortDataRecursive(data, sortedEntries) { // 对当前层级进行排序 let sortedData = [...data]; // 逐层应用排序条件 for (const [key, { direction }] of sortedEntries) { if (direction === 'none') continue; const cellHeader = this.getColumnByKey(key); if (!cellHeader || !cellHeader.column.sortBy) continue; // 对当前层级进行单列排序 sortedData = this.applySingleColumnSort(sortedData, key, direction, cellHeader.column.sortBy); } // 递归处理子节点 return sortedData.map((item) => { if (item.children && Array.isArray(item.children)) { item.children = this.sortDataRecursive(item.children, sortedEntries); } return item; }); } /** * 对数组进行单列排序 * @param data 要排序的数据 * @param key 排序的列键 * @param direction 排序方向 * @param sortBy 排序类型 * @returns 排序后的数据 */ applySingleColumnSort(data, key, direction, sortBy) { return data.sort((a, b) => { const aValue = a[key]; const bValue = b[key]; let comparison = 0; if (typeof sortBy === 'function') { comparison = sortBy(a, b); } else if (sortBy === 'number') { const aNum = Number(aValue) || 0; const bNum = Number(bValue) || 0; comparison = aNum - bNum; } else if (sortBy === 'string') { const aStr = String(aValue || ''); const bStr = String(bValue || ''); comparison = aStr.localeCompare(bStr); } else if (sortBy === 'date') { comparison = compareDates(aValue, bValue); } return direction === 'asc' ? comparison : -comparison; }); } /** * 根据rowKey,控制指定展开行 * @param rowKey * @param expand */ expandItem(rowKey, expand = false) { const row = this.rowKeyMap.get(rowKey); row.expand = expand; this.expandMap.set(rowKey, expand); this.clearBufferData(); // 清除缓存数据 this.ctx.emit('draw'); } setExpandRowKeys(rowKeys, expand = true) { this.expandMap.clear(); rowKeys.forEach((rowkey) => { const row = this.rowKeyMap.get(rowkey); this.expandMap.set(rowkey, expand); row.expand = expand; }); this.clearBufferData(); // 清除缓存数据 this.ctx.emit('draw'); } getExpandRowKeys() { let list = []; this.rowKeyMap.forEach((row, key) => { if (row.expand) { list.push(key); } }); return list; } expandAll(expand) { this.expandMap.clear(); this.rowKeyMap.forEach((row) => { row.expand = expand; this.expandMap.set(row.key, expand); }); this.clearBufferData(); // 清除缓存数据 this.ctx.emit('draw'); } expandLoading(rowKey, loading = false) { const row = this.rowKeyMap.get(rowKey); row.expandLoading = loading; this.clearBufferData(); // 清除缓存数据 this.ctx.emit('draw'); } setExpandChildren(rowKey, children) { const row = this.rowKeyMap.get(rowKey); row.expand = true; this.expandMap.set(rowKey, true); row.expandLazy = true; row.item.children = children; this.initData(row.item.children, row.level + 1); this.clearBufferData(); // 清除缓存数据 } getIsExpandLoading(rowKey) { const row = this.rowKeyMap.get(rowKey); return row.expandLoading; } getIsExpandLazy(rowKey) { const row = this.rowKeyMap.get(rowKey); return row.expandLazy; } /** * 根据rowKey获取是否展开 * @param rowKey * @returns */ getIsExpand(rowKey) { const row = this.rowKeyMap.get(rowKey); return row.expand; } /** * 根据rowKey获取行数据 * @param rowKey * @returns */ getRowForRowKey(rowKey) { return this.rowKeyMap.get(rowKey); } getRowForRowIndex(rowIndex) { const rowKey = this.getRowKeyForRowIndex(rowIndex); return this.rowKeyMap.get(rowKey); } /** * 根据rowIndex获取rowKey * @param rowKey * @returns */ getRowKeyForRowIndex(rowIndex) { return this.rowIndexRowKeyMap.get(rowIndex) || ''; } getRowKeyByItem(item) { return this.itemRowKeyMap.get(item); } getRowIndexForRowKey(rowKey) { return this.rowKeyRowIndexMap.get(rowKey); } /** * 根据rowIndex和colIndex获取单元格数据 * @param rowIndex * @param colIndex * @returns */ getItemValueForRowIndexAndColIndex(rowIndex, colIndex) { const has = this.rowIndexRowKeyMap.has(rowIndex) && this.colIndexKeyMap.get(colIndex); if (!has) { return null; } const rowKey = this.rowIndexRowKeyMap.get(rowIndex); const key = this.colIndexKeyMap.get(colIndex); if (rowKey === undefined || key === undefined) { return null; } return { rowKey, key, value: this.getItemValue(rowKey, key), }; } /** * 根据rowKey和key获取单元格数据 * @param rowKey * @param key * @returns */ getItemValue(rowKey, key) { const row = this.rowKeyMap.get(rowKey); if (row && row.item) { if (row.item[key] === undefined) { return null; } return row.item[key]; } return null; } /** * 批量设置数据 * @param list * @param history * @returns */ async batchSetItemValue(_list, history = false, checkReadonly = true, historyAcion = 'none') { let historyList = []; let _checkReadonly = checkReadonly; const rowKeyList = new Set(); let errList = []; let changeList = _list.map((item) => { const { rowKey, key } = item; let _value = item.value; let value = _value; const row = this.getRowDataItemForRowKey(rowKey); const oldValue = this.getItemValue(rowKey, key); // 判断数字 const cell = this.getVirtualBodyCellByKey(rowKey, key); if (cell?.type === 'number') { // 处理数字 if (['', undefined, null].includes(_value)) { value = null; } else if (/^-?\d+(\.\d+)?$/.test(`${_value}`)) { value = Number(_value); } else { value = oldValue; errList.push({ ...item, value, oldValue, row, }); } } return { ...item, value, oldValue, row, }; }); // 过滤错误的 changeList = changeList.filter((item) => { return !errList.some((err) => item.rowKey === err.rowKey && item.key === err.key); }); if (errList.length) { const err = { code: 'ERR_BATCH_SET_NUMBER_VALUE', message: 'Assignment failed, not a numeric type', data: errList, }; this.ctx.emit('error', err); } // 过滤旧数据新数据相同的 changeList = changeList.filter((item) => item.oldValue !== item.value); if (!changeList.length) { return; } const { BEFORE_VALUE_CHANGE_METHOD } = this.ctx.config; if (historyAcion === 'none' && typeof BEFORE_VALUE_CHANGE_METHOD === 'function') { const beforeCellValueChange = BEFORE_VALUE_CHANGE_METHOD; const values = await beforeCellValueChange(changeList); changeList = values; _checkReadonly = false; // 允许编辑只读 } changeList.forEach((data) => { const { value, rowKey, key } = data; const oldValue = this.getItemValue(rowKey, key); rowKeyList.add(rowKey); // 不加历史,不重绘,不是编辑器,不检验只读 this.setItemValue(rowKey, key, value, false, false, false, _checkReadonly); historyList.push({ rowKey, key, oldValue, newValue: value, }); }); // 触发change事件 let rows = []; rowKeyList.forEach((rowKey) => { rows.push(this.ctx.database.getRowDataItemForRowKey(rowKey)); }); const promsieValidators = changeList.map(({ rowKey, key }) => this.getValidator(rowKey, key)); Promise.all(promsieValidators).then(() => { if (this.validationErrorMap.size === 0 && this.changedDataMap.size > 0) { this.ctx.emit('validateChangedData', this.getChangedData()); } }); const changeListValid = changeList.map((item) => { const errorTip = !!this.getValidationError(item.rowKey, item.key).length; return { ...item, errorTip, }; }); this.ctx.emit('change', changeListValid, rows); // 推历史记录 if (history) { this.ctx.history.pushState({ changeList: historyList, scrollX: this.ctx.scrollX, scrollY: this.ctx.scrollY, type: 'multiple', }); } this.ctx.emit('draw'); } /** *设置单一数据 * @param rowKey * @param key * @param value * @param history 是否添加历史记录 * @param reDraw 是否刷新重绘 * @param isEditor 是否是编辑器 * @param checkReadonly 是否检查只读 * @returns */ async setItemValue(rowKey, key, _value, history = false, reDraw = false, isEditor = false, checkReadonly = true) { // 异常情况 if (!this.rowKeyMap.has(rowKey)) { return {}; } const { item } = this.rowKeyMap.get(rowKey); let oldValue = item[key]; let value = _value; // 只读返回旧值 if (checkReadonly && this.ctx.database.getReadonly(rowKey, key)) { return { oldValue, newValue: oldValue, }; } // 处理对象 if (item[key] !== null && typeof item[key] === 'object') { oldValue = JSON.parse(JSON.stringify(item[key])); } const changeKey = `${rowKey}\u200b_${key}`; // 设置原始值,只设置一次 if (!this.originalDataMap.has(changeKey)) { this.originalDataMap.set(changeKey, oldValue); } const originalValue = this.originalDataMap.get(changeKey); const row = this.getRowDataItemForRowKey(rowKey); // 是否是否是编辑器进来的 if (isEditor) { const cell = this.getVirtualBodyCellByKey(rowKey, key); if (cell?.type === 'number') { // 处理数字 if (['', undefined, null].includes(_value)) { value = null; } else if (/^-?\d+(\.\d+)?$/.test(`${_value}`)) { value = Number(_value); } else { value = oldValue; const err = { code: 'ERR_SET_NUMBER_VALUE', message: 'Assignment failed, not a numeric type', data: [ { rowKey, key, value, oldValue, row, }, ], }; this.ctx.emit('error', err); } } if (value === oldValue) { return { oldValue, newValue: oldValue, }; } let changeList = [ { rowKey, key, value, oldValue, row, }, ]; this.batchSetItemValue(changeList, history, false); this.ctx.emit('editChange', { rowKey, key, oldValue, value, originalValue, row, }); } else { this.changedDataMap.set(changeKey, value); item[key] = value; } // 迭代改变值事件,有改变一次值就触发,包括批量的 if (this.ctx.hasEvent('iterationChange')) { this.ctx.emit('iterationChange', { rowKey, key, oldValue, value, originalValue: this.originalDataMap.get(changeKey), row, }); } // 重绘 if (reDraw) { this.ctx.emit('draw'); } return { oldValue, newValue: value, }; } /** * 根据rowKey 获取行数据 * @param rowKey * @returns */ getRowDataItemForRowKey(rowKey) { if (!this.rowKeyMap.has(rowKey)) { return {}; } const { item } = this.rowKeyMap.get(rowKey); return item; } /** * * @param rowKey 设置选中状态ByCheckboxKey,用于合并Checkbox单元格时声明唯一key * @param check */ setRowSelectionByCheckboxKey(rowKey, check) { const { CHECKBOX_KEY } = this.ctx.config; if (CHECKBOX_KEY) { if (!this.rowKeyMap.has(rowKey)) { return false; } const { item } = this.rowKeyMap.get(rowKey); const checkboxKey = item[CHECKBOX_KEY]; if (this.checkboxKeyMap.has(checkboxKey)) { const rowKeys = this.checkboxKeyMap.get(checkboxKey) || []; rowKeys.forEach((rowKey) => { const selection = this.selectionMap.get(rowKey); if (selection) { selection.check = check; } }); } } } /** * 根据rowKey 取反选中 * @param rowKey */ toggleRowSelection(rowKey, cellType) { const row = this.rowKeyMap.get(rowKey); const selection = this.selectionMap.get(rowKey); if (!selection) { return; } // 检查是否是树形选择列 if (cellType === 'selection-tree' || cellType === 'tree-selection') { this.toggleTreeSelection(rowKey); } else { selection.check = !selection.check; this.setRowSelectionByCheckboxKey(rowKey, selection.check); } this.ctx.emit('toggleRowSelection', row); const rows = this.getSelectionRows(); this.ctx.emit('selectionChange', rows); // 清除缓存 this.bufferCheckState.buffer = false; this.ctx.emit('draw'); } // 切换树形选择状态 toggleTreeSelection(rowKey) { const treeState = this.getTreeSelectionState(rowKey); const mode = this.ctx.config.TREE_SELECT_MODE; if (mode === 'auto') { // auto模式:子项全不选->父项不勾选,子项全选->父项勾选,子项都有->父项半选 // 父项选中的情况点击清空父项选择和所有子项选择,其他情况点击父项,勾选父项和所有子项选择(递归) if (treeState.checked && !treeState.indeterminate) { // 递归取消所有子项 this.clearTreeSelectionRecursive(rowKey); // 如果已全选,则取消选中 this.setRowSelection(rowKey, false, false); } else { // 递归选中所有子项 this.selectTreeSelectionRecursive(rowKey); // 如果未选中或半选,则选中 this.setRowSelection(rowKey, true, false); } } else if (mode === 'cautious') { // cautious模式:交互上相同,但是半选是不算在数据里面的 if (treeState.checked && !treeState.indeterminate) { // 递归取消所有子项 this.clearTreeSelectionRecursive(rowKey); // 如果已全选,则取消选中 this.setRowSelection(rowKey, false, false); } else { // 递归选中所有子项 this.selectTreeSelectionRecursive(rowKey); // 如果未选中或半选,则选中 this.setRowSelection(rowKey, true, false); } } else if (mode === 'strictly') { // strictly模式:父子各选各的互相不干扰,没有半选模式 const selection = this.selectionMap.get(rowKey); if (selection) { selection.check = !selection.check; this.setRowSelectionByCheckboxKey(rowKey, selection.check); } } // 触发选择变化事件 this.ctx.emit('selectionChange', this.getSelectionRows()); this.ctx.emit('draw'); } // 递归选中树形选择 selectTreeSelectionRecursive(rowKey) { const children = this.getTreeChildren(rowKey); children.forEach((childKey) => { this.setRowSelectionByParent(childKey, true); this.selectTreeSelectionRecursive(childKey); }); } // 递归取消树形选择 clearTreeSelectionRecursive(rowKey) { const children = this.getTreeChildren(rowKey); children.forEach((childKey) => { this.setRowSelectionByParent(childKey, false); this.clearTreeSelectionRecursive(childKey); }); } // 向上递归更新父项状态 updateParentTreeSelection(rowKey) { const parentKey = this.getTreeParent(rowKey); if (!parentKey) { return; } // 获取父项的所有直接子项 const children = this.getTreeChildren(parentKey); const childSelections = children.map((childKey) => this.selectionMap.get(childKey)); const checkedChildren = childSelections.filter((s) => s?.check).length; const totalChildren = childSelections.length; // 计算父项应该的状态 let parentShouldBeChecked = false; if (totalChildren > 0) { if (checkedChildren === 0) { // 所有子项都未选中,父项应该未选中 parentShouldBeChecked = false; } else if (checkedChildren === totalChildren) { // 所有子项都选中,父项应该选中 parentShouldBeChecked = true; } else { // 部分子项选中,父项应该半选 if (this.ctx.config.TREE_SELECT_MODE === 'auto') { // auto模式下半选算作选中 parentShouldBeChecked = true; } else if (this.ctx.config.TREE_SELECT_MODE === 'cautious') { // cautious模式下半选不算作选中 parentShouldBeChecked = false; } } } // 更新父项状态 const parentSelection = this.selectionMap.get(parentKey); if (parentSelection && parentSelection.check !== parentShouldBeChecked) { parentSelection.check = parentShouldBeChecked; this.setRowSelectionByCheckboxKey(parentKey, parentShouldBeChecked); // 递归更新更上层的父项 this.updateParentTreeSelection(parentKey); } } /** * 根据rowKey 设置选中状态 * @param rowKey */ setRowSelection(rowKey, check, draw = true) { const selection = this.selectionMap.get(rowKey); if (!selection) { return; } selection.check = check; this.setRowSelectionByCheckboxKey(rowKey, selection.check); this.ctx.emit('setRowSelection', check, selection.row); // 如果是树形选择模式,需要向上递归更新父项状态 if (this.ctx.config.TREE_SELECT_MODE === 'auto' || this.ctx.config.TREE_SELECT_MODE === 'cautious') { this.updateParentTreeSelection(rowKey); } if (draw) { // 清除缓存 this.bufferCheckState.buffer = false; this.ctx.emit('draw'); } } setRowSelectionByParent(rowKey, check) { const selection = this.selectionMap.get(rowKey); if (!selection) { return; } selection.check = check; this.setRowSelectionByCheckboxKey(rowKey, selection.check); } getSelectionRows() { let rows = []; this.selectionMap.forEach((selection) => { if (selection.check) { rows.push(selection.row); } }); return rows; } /** * 根据rowKey 获取选中状态 * @param rowKey */ getRowSelection(rowKey) { const selection = this.selectionMap.get(rowKey); if (!selection) { return false; } return selection.check; } // 获取树形选择状态 getTreeSelectionState(rowKey) { const row = this.getRowForRowKey(rowKey); if (!row) { return { checked: false, indeterminate: false }; } const selectionMap = this.selectionMap.get(rowKey); const checked = selectionMap?.check || false; // 计算半选状态 const children = this.getTreeChildren(rowKey); if (children.length === 0) { return { checked, indeterminate: false }; } let indeterminate = false; let finalChecked = checked; if (this.ctx.config.TREE_SELECT_MODE === 'auto') { // auto模式:子项全不选->父项不勾选,子项全选->父项勾选,子项都有->父项半选 // 其中半选是算在最终选择的数据里面的 // 递归计算所有后代的状态 const getAllDescendantsRecursive = (parentKey) => { const children = this.getTreeChildren(parentKey); let allDescendants = []; for (const childKey of children) { allDescendants.push(childKey); allDescendants.push(...getAllDescendantsRecursive(childKey)); } return allDescendants; }; const allDescendants = getAllDescendantsRecursive(rowKey); const descendantSelections = allDescendants.map((descKey) => this.selectionMap.get(descKey)); const checkedDescendants = descendantSelections.filter((s) => s?.check).length; const totalDescendants = descendantSelections.length; const someChecked = checkedDescendants > 0; const allChecked = checkedDescendants === totalDescendants; indeterminate = someChecked && !allChecked; finalChecked = checked || someChecked; // 自身选中或有后代选中就算选中(包括半选) // 特殊处理:如果父项被选中但所有后代都被取消选中,则父项也取消选中 if (checked && totalDescendants > 0 && checkedDescendants === 0) { finalChecked = false; indeterminate = false; } } else if (this.ctx.config.TREE_SELECT_MODE === 'cautious') { // cautious模式:交互上相同,但是半选是不算在数据里面的 // 递归计算所有后代的状态 const getAllDescendantsRecursive = (parentKey) => { const children = this.getTreeChildren(parentKey); let allDescendants = []; for (const childKey of children) { allDescendants.push(childKey); allDescendants.push(...getAllDescendantsRecursive(childKey)); } return allDescendants; }; const allDescendants = getAllDescendantsRecursive(rowKey); const descendantSelections = allDescendants.map((descKey) => this.selectionMap.get(descKey)); const checkedDescendants = descendantSelections.filter((s) => s?.check).length; const totalDescendants = descendantSelections.length; const someChecked = checkedDescendants > 0; const allChecked = checkedDescendants === totalDescendants; indeterminate = someChecked && !allChecked; finalChecked = checked || allChecked; // 只有全选才算选中,半选不算选中 // 特殊处理:如果父项被选中但所有后代都被取消选中,则父项也取消选中 if (checked && totalDescendants > 0 && checkedDescendants === 0) { finalChecked = false; indeterminate = false; } } else if (this.ctx.config.TREE_SELECT_MODE === 'strictly') { // strictly模式:父子各选各的互相不干扰,没有半选模式 indeterminate = false; finalChecked = checked; } const result = { checked: finalChecked, indeterminate }; return result; } // 获取树形子节点 getTreeChildren(rowKey) { const row = this.getRowForRowKey(rowKey); if (!row || !row.item || !row.item.children) { return []; } const children = []; const collectChildren = (items) => { for (const item of items) { const itemKey = this.getRowKeyByItem(item); if (itemKey) { children.push(itemKey); } if (item.children && item.children.length > 0) { collectChildren(item.children); } } }; collectChildren(row.item.children); return children; } // 获取树形父节点 getTreeParent(rowKey) { const findParent = (data, targetKey) => { for (const item of data) { const itemKey = this.getRowKeyByItem(item); if (item.children) { for (const child of item.children) { const childKey = this.getRowKeyByItem(child); if (childKey === targetKey) { return itemKey; } const result = findParent(item.children, targetKey); if (result) { return result; } } } } return null; }; return findParent(this.data, rowKey); } /** * 根据rowKey 获取选中状态 * @param rowKey */ getRowSelectable(rowKey) { const { selectable, item, rowIndex } = this.rowKeyMap.get(rowKey); if (typeof selectable === 'function') { return selectable({ row: item, rowIndex, }); } return selectable; } /** * 全选 * @param rowKey */ toggleAllSelection() { // 检查是否是树形选择模式 const isTreeSelectionMode = this.ctx.config.TREE_SELECT_MODE === 'auto' || this.ctx.config.TREE_SELECT_MODE === 'cautious'; if (isTreeSelectionMode) { // 树形选择模式:只从上到下设置,跳过递归计算 this.rowKeyMap.forEach((row, rowKey) => { let _selectable = row.selectable; if (typeof _selectable === 'function') { _selectable = _selectable({ row: row.item, rowIndex: row.rowIndex, }); } if (_selectable) { // 直接设置选中状态,跳过递归计算 const selection = this.selectionMap.get(rowKey); if (selection) { selection.check = true; this.setRowSelectionByCheckboxKey(rowKey, true); } } }); } else { // 普通选择模式:使用原有逻辑 this.rowKeyMap.forEach((row, rowKey) => { let _selectable = row.selectable; if (typeof _selectable === 'function') { _selectable = _selectable({ row: row.item, rowIndex: row.rowIndex, }); } if (_selectable) { this.setRowSelection(rowKey, true, false); } }); } const rows = this.getSelectionRows(); this.ctx.emit('toggleAllSelection', rows); this.ctx.emit('selectionChange', rows); // 清除缓存 this.bufferCheckState.buffer = false; this.ctx.emit('draw'); } /** * 清除选中 * @param rowKey */ clearSelection(ignoreReserve = false) { // 检查是否是树形选择模式 const isTreeSelectionMode = this.ctx.config.TREE_SELECT_MODE === 'auto' || this.ctx.config.TREE_SELECT_MODE === 'cautious'; // 清除选中,点击表头清除时要忽略跨页选的 if (ignoreReserve) { if (isTreeSelectionMode) { // 树形选择模式:只从上到下设置,跳过递归计算 this.rowKeyMap.forEach((_, rowKey) => { const selection = this.selectionMap.get(rowKey); if (selection) { selection.check = false; this.setRowSelectionByCheckboxKey(rowKey, false); } }); } else { // 普通选择模式:使用原有逻辑 this.rowKeyMap.forEach((_, rowKey) => { this.setRowSelection(rowKey, false, false); }); } } else { this.selectionMap.clear(); this.rowKeyMap.forEach((row, rowKey) => { this.selectionMap.set(rowKey, { check: false, row: row.item, key: rowKey, }); }); } const rows = this.getSelectionRows(); this.ctx.emit('clearSelection'); this.ctx.emit('selectionChange', rows); // 清除缓存 this.bufferCheckState.buffer = false; this.ctx.emit('draw'); } /** * 获取选中状态,表头用 * @param rowKey */ getCheckedState() { // 缓存,解决性能问题 const { buffer, ...bufferState } = this.bufferCheckState; if (buffer) { return bufferState; } const total = this.rowKeyMap.size; let totalChecked = 0; let totalSelectable = 0; const reserveTotal = this.selectionMap.size; const reserveHasChecked = Array.from(this.selectionMap.values()).some((item) => item.check); this.rowKeyMap.forEach((row, rowKey) => { if (this.selectionMap.get(rowKey)?.check) { totalChecked += 1; } let _selectable = row.selectable; if (typeof _selectable === 'function') { _selectable = _selectable({ row: row.item, rowIndex: row.rowIndex, }); } if (_selectable) { totalSelectable += 1; } }); // 存在跨页时,变更半选中 const reserveIndeterminate = reserveTotal > total && totalChecked === 0 && reserveHasChecked; const indeterminate = (totalSelectable && totalSelectable > totalChecked && totalChecked > 0) || reserveIndeterminate; const selectable = totalSelectable !== 0; const check = !!totalSelectable && totalSelectable === totalChecked; // 缓存 this.bufferCheckState = { buffer: true, check, indeterminate, selectable, }; return { check, indeterminate, selectable, }; } /** * 更新列索引 * @param list 表头末级列表 */ updateColIndexKeyMap(list = []) { this.colIndexKeyMap.clear(); list.forEach((column) => { this.colIndexKeyMap.set(column.colIndex, column.key); }); } getColumnByColIndex(colIndex) { const key = this.colIndexKeyMap.get(colIndex); if (key && this.headerMap.has(key)) { return this.headerMap.get(key)?.column; } } getColumnByKey(key) { const column = this.headerMap.get(key); if (column) { return column; } } getColIndexForKey(key) { if (key && this.headerMap.has(key)) { return this.headerMap.get(key)?.colIndex; } } getColHeaderByIndex(colIndex) { const key = this.colIndexKeyMap.get(colIndex); if (key && this.headerMap.has(key)) { return this.headerMap.get(key); } return undefined; } /** * 获取以改变数据 */ getChangedData() { let