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,347 lines (1,346 loc) 58.8 kB
import BaseCell from './BaseCell'; import CellImage from './CellImage'; import { shouldDrawFullCellBorder, shouldDrawInternalHorizontalBorder } from './BorderStyle'; export default class Cell extends BaseCell { constructor(ctx, rowIndex, colIndex, x, y, width, height, column, row, cellType = 'body', isUpdate = true) { super(ctx, x, y, width, height, cellType, column.fixed); Object.defineProperty(this, "parentRowKey", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "parentRowKeys", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "formatter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "formatterFooter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "hoverIconName", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "operation", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "align", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "verticalAlign", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "fixed", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "type", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "editorType", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "editorProps", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "cellType", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "level", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "colspan", { enumerable: true, configurable: true, writable: true, value: 1 }); Object.defineProperty(this, "rowspan", { enumerable: true, configurable: true, writable: true, value: 1 }); Object.defineProperty(this, "mergeRow", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "mergeCol", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "relationRowKeys", { enumerable: true, configurable: true, writable: true, value: [] }); // 合并单元格关联key Object.defineProperty(this, "relationColKeys", { enumerable: true, configurable: true, writable: true, value: [] }); // 合并单元格关联key Object.defineProperty(this, "key", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "column", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "rowIndex", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "colIndex", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "rowKey", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "row", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "value", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "render", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "renderFooter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "style", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "domDataset", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(this, "rules", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "message", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "text", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "displayText", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "visibleWidth", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "visibleHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "isHasChanged", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "drawX", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "drawY", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "drawCellBgColor", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "drawCellSkyBgColor", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "drawTextColor", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "drawTextFont", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "drawTextX", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "drawTextY", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "drawTextWidth", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "drawTextHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "autoRowHeight", { enumerable: true, configurable: true, writable: true, value: false }); // 是否启用行高自适应 Object.defineProperty(this, "calculatedHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); // 计算出的自适应高度 Object.defineProperty(this, "ellipsis", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "rowExpand", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "rowHasChildren", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "overflowTooltipShow", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "selectorCellValueType", { enumerable: true, configurable: true, writable: true, value: 'value' }); Object.defineProperty(this, "overflowTooltipMaxWidth", { enumerable: true, configurable: true, writable: true, value: 500 }); Object.defineProperty(this, "overflowTooltipPlacement", { enumerable: true, configurable: true, writable: true, value: 'top' }); Object.defineProperty(this, "maxLineClamp", { enumerable: true, configurable: true, writable: true, value: 'auto' }); Object.defineProperty(this, "precision", { enumerable: true, configurable: true, writable: true, value: undefined }); Object.defineProperty(this, "min", { enumerable: true, configurable: true, writable: true, value: undefined }); Object.defineProperty(this, "max", { enumerable: true, configurable: true, writable: true, value: undefined }); Object.defineProperty(this, "maxlength", { enumerable: true, configurable: true, writable: true, value: undefined }); Object.defineProperty(this, "mixedRender", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "renderType", { enumerable: true, configurable: true, writable: true, value: 'default' }); Object.defineProperty(this, "renderFooterType", { enumerable: true, configurable: true, writable: true, value: 'default' }); this.visibleWidth = this.width; this.visibleHeight = this.height; this.colIndex = colIndex; this.rowIndex = rowIndex; this.key = column.key; this.type = column.type || ''; this.editorType = column.editorType || 'text'; this.selectorCellValueType = column.selectorCellValueType || this.ctx.config.SELECTOR_CELL_VALUE_TYPE || 'value'; this.editorProps = column.editorProps || {}; this.cellType = cellType; this.align = column.align || this.ctx.config.COLUMNS_ALIGN; this.verticalAlign = column.verticalAlign || this.ctx.config.COLUMNS_VERTICAL_ALIGN; this.fixed = column.fixed || ''; this.level = column.level || 0; this.operation = column.operation || false; this.column = column; this.rules = column.rules || []; this.row = row; this.rowKey = this.cellType === 'body' ? this.ctx.database.getRowKeyForRowIndex(rowIndex) : `${this.cellType}_${this.rowIndex}`; this.value = this.getValue(); this.render = column.render; this.overflowTooltipShow = column.overflowTooltipShow === false ? false : true; this.autoRowHeight = column.autoRowHeight !== undefined ? column.autoRowHeight : this.ctx.config.AUTO_ROW_HEIGHT; this.overflowTooltipMaxWidth = column.overflowTooltipMaxWidth || 500; this.overflowTooltipPlacement = column.overflowTooltipPlacement || 'top'; this.renderFooter = column.renderFooter; this.hoverIconName = column.hoverIconName; this.formatter = column.formatter; this.formatterFooter = column.formatterFooter; this.maxLineClamp = column.maxLineClamp || 'auto'; this.precision = column.precision; this.min = column.min; this.max = column.max; this.maxlength = column.maxlength; this.mixedRender = column.mixedRender || false; this.renderType = column.renderType || 'default'; this.renderFooterType = column.renderFooterType || 'default'; const rowItem = this.ctx.database.getRowForRowKey(this.rowKey); if (this.cellType === 'body' && rowItem) { this.parentRowKey = rowItem.parentRowKey; this.parentRowKeys = rowItem.parentRowKeys; } if (isUpdate) { this.update(); } } setWidthHeight(width, height) { this.width = width; this.height = height; } getValidationMessage() { const errors = this.ctx.database.getValidationError(this.rowKey, this.key); if (Array.isArray(errors) && errors.length) { const [err] = errors; this.message = err.message || ''; } return this.message; } update() { this.drawX = this.getDrawX(); this.drawY = this.getDrawY(); this.updateSpan(); this.updateDragImage(); const dragImage = this.getImage('drag'); const drawDragImageWidth = dragImage ? dragImage.width : 0; this.drawTextX = this.drawX + drawDragImageWidth; this.drawTextY = this.drawY; this.isHasChanged = this.ctx.database.isHasChangedData(this.rowKey, this.key); this.drawTextWidth = this.visibleWidth - drawDragImageWidth; this.drawTextHeight = this.visibleHeight; this.updateStyle(); this.updateType(); this.updateHoverIcon(); this.updateSelection(); this.updateTree(); this.updateEditor(); this.updateRender(); this.getValidationMessage(); this.updateContainer(); this.text = this.getText(); this.displayText = this.getDisplayText(); } updateSpan() { // 合计不合并 if (this.cellType === 'footer') { return; } const { SPAN_METHOD } = this.ctx.config; if (typeof SPAN_METHOD === 'function') { const spanMethod = SPAN_METHOD; const { colspan = 1, rowspan = 1, relationRowKeys, relationColKeys, mergeRow = false, mergeCol = false, } = spanMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), headIndex: this.ctx.body.headIndex, headPosition: this.ctx.database.getPositionForRowIndex(this.ctx.body.headIndex), visibleRows: this.ctx.body.visibleRows, visibleLeafColumns: this.ctx.header.visibleLeafColumns, rows: this.ctx.body.data, }) || {}; if (Array.isArray(relationRowKeys) && relationRowKeys.length > 0) { this.relationRowKeys = relationRowKeys; } else { this.relationRowKeys = [this.key]; } if (Array.isArray(relationColKeys) && relationColKeys.length > 0) { this.relationColKeys = relationColKeys; } else { this.relationColKeys = [this.key]; } this.mergeCol = mergeCol; this.mergeRow = mergeRow; this.colspan = colspan; this.rowspan = rowspan; this.visibleWidth = this.getWidthByColIndexColSpan(this.colIndex, this.colspan); this.visibleHeight = this.ctx.database.getHeightByRowIndexRowSpan(this.rowIndex, this.rowspan); } } updateSpanInfo() { // 列合并单元格 if (this.mergeRow || this.mergeCol) { const spanInfo = this.getSpanInfo(); this.height = spanInfo.height; this.width = spanInfo.width; this.drawX = this.getDrawX(); this.drawY = this.getDrawY(); this.drawY -= spanInfo.offsetTop; this.drawX -= spanInfo.offsetLeft; } } updateType() { // 更改类型 const { BODY_CELL_TYPE_METHOD } = this.ctx.config; if (typeof BODY_CELL_TYPE_METHOD === 'function') { const cellTypeMethod = BODY_CELL_TYPE_METHOD; const type = cellTypeMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); // 可以动态改变类型 if (type !== undefined) { this.type = type; } } } updateEditor() { // 更改类型 const { BODY_CELL_EDITOR_METHOD } = this.ctx.config; if (typeof BODY_CELL_EDITOR_METHOD === 'function') { const CellEditorMethod = BODY_CELL_EDITOR_METHOD; const editorProps = CellEditorMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); // 可以动态改变类型 if (editorProps !== undefined) { const { type, props = {} } = editorProps; this.editorType = type; this.editorProps = props; } } } updateRender() { const { BODY_CELL_RENDER_METHOD } = this.ctx.config; if (typeof BODY_CELL_RENDER_METHOD === 'function') { const cellRenderMethod = BODY_CELL_RENDER_METHOD; const render = cellRenderMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); // 可以动态改变类型 if (render !== undefined) { this.render = render; } } } validate() { this.ctx.database .getValidator(this.rowKey, this.key) .then(() => { this.ctx.database.setValidationError(this.rowKey, this.key, []); this.message = ''; }) .catch((errors) => { if (Array.isArray(errors) && errors.length) { const [err] = errors; this.message = err.message; this.ctx.database.setValidationError(this.rowKey, this.key, errors); } }) .finally(() => { this.ctx.emit('draw'); }); } /** * 更新样式 */ updateStyle() { if (this.autoRowHeight) { // 自适应行高 this.domDataset = { 'data-auto-height': true, 'data-row-index': this.rowIndex, 'data-col-index': this.colIndex, }; } this.style = this.getOverlayerViewsStyle(); } isInsideVisible(x, y) { return x >= this.drawX && x <= this.drawX + this.visibleWidth && y >= this.drawY && y <= this.drawY + this.visibleHeight; } updateTree() { const { CELL_PADDING = 0 } = this.ctx.config; const { rowKey, cellType } = this; let icon = undefined; let iconOffsetX = 0; let iconName = ''; if (!(['tree', 'selection-tree', 'tree-selection'].includes(this.type) && cellType === 'body')) { return; } const row = this.ctx.database.getRowForRowKey(rowKey); const { expand = false, hasChildren = false, expandLoading = false, level = 0 } = row || {}; this.rowExpand = expand; this.rowHasChildren = hasChildren; // 计算树形图标的偏移量 const { TREE_INDENT = 16, CHECKBOX_SIZE, TREE_ICON_SIZE } = this.ctx.config; iconOffsetX = level * TREE_INDENT; if (expandLoading) { const loadingIcon = this.ctx.icons.get('loading'); iconName = 'loading'; icon = loadingIcon; } else if (hasChildren) { const expandIcon = this.ctx.icons.get('expand'); const shrinkIcon = this.ctx.icons.get('shrink'); icon = !expand ? expandIcon : shrinkIcon; iconName = !expand ? 'expand' : 'shrink'; } let iconWidth = TREE_ICON_SIZE; let iconHeight = TREE_ICON_SIZE; let drawX = this.drawX; if (this.align === 'center' || this.align === 'right') { drawX = this.drawX + (this.visibleWidth - iconWidth - 2 * CELL_PADDING) / 2; // 居中对齐,改成左对齐 this.align = 'left'; } let iconX = drawX + iconOffsetX + CELL_PADDING; let iconY = this.drawY + (this.visibleHeight - iconHeight) / 2; let drawTextX = iconOffsetX + this.drawX + iconWidth - 0.5; const selectionImage = this.getImage('selection'); const dragImage = this.getImage('drag'); const dragImageWidth = dragImage ? dragImage.width : 0; if (this.type === 'selection-tree' && selectionImage) { const selectionImageX = selectionImage.x; const selectionImageWidth = selectionImage.width; // 树形图标在左侧,checkbox 在树形图标右侧 iconX = iconOffsetX + selectionImageX + selectionImageWidth; drawTextX = iconX + iconWidth - CELL_PADDING / 2; } else if (this.type === 'tree-selection') { // 树形选择,两个图标宽度,文本已经有CELL_PADDING间距,/2看起来好看些 iconX += dragImageWidth; drawTextX = iconX + CHECKBOX_SIZE + iconWidth - CELL_PADDING / 2; } else { // 普通tree,文本已经有CELL_PADDING间距,/2看起来好看些 iconX += dragImageWidth; drawTextX = iconX + iconWidth - CELL_PADDING / 2; } // 更改文本距离 this.drawTextX = drawTextX; this.drawTextWidth = this.drawX + this.visibleWidth - drawTextX; // 减去树形图标的宽度 // 判断是否溢出格子 if (iconX + iconWidth + CELL_PADDING > this.drawX + this.visibleWidth) { return; } if (iconY + iconHeight + CELL_PADDING > this.drawY + this.visibleHeight) { return; } // 不论是否需要绘制图标,都更新图标的“基准位置”,供树线使用 this.setImage('tree', new CellImage(iconName, iconX, iconY, iconWidth, iconHeight, icon)); // 树连线仅在绘制阶段调用,避免在 update 阶段被清屏 } updateDragImage() { if (this.cellType === 'body' && this.column.dragRow) { const { DRAG_ROW_ICON_SIZE, CELL_PADDING } = this.ctx.config; const dragImageX = this.drawX + CELL_PADDING / 2; const dragImageY = this.drawY + (this.visibleHeight - DRAG_ROW_ICON_SIZE) / 2; const dragImage = new CellImage('drag', dragImageX, dragImageY, DRAG_ROW_ICON_SIZE, DRAG_ROW_ICON_SIZE, this.ctx.icons.get('drag')); if (this.rowspan === 0) { dragImage.setVisible(false); } this.setImage('drag', dragImage); } } drawTreeLine() { const { TREE_LINE, TREE_INDENT = 16, TREE_ICON_SIZE = 16, TREE_LINE_COLOR = '#e1e6eb' } = this.ctx.config; // 仅 body 且树类型才绘制 if (!TREE_LINE || this.cellType !== 'body') return; if (!['tree', 'selection-tree', 'tree-selection'].includes(this.type)) return; if (this.rowspan === 0 || this.colspan === 0) return; const row = this.ctx.database.getRowForRowKey(this.rowKey) || {}; const level = row.level ?? 0; // 以当前树图标为中心点 const treeImage = this.getImage('tree'); if (!treeImage) return; const treeImageX = treeImage.x; const treeImageY = treeImage.y; const treeImageWidth = treeImage.width; const treeImageHeight = treeImage.height; const iconCenterX = treeImageX + treeImageWidth / 2; const iconCenterY = treeImageY + treeImageHeight / 2; // 基于已计算的树图标位置反推基准点:当前图标左侧减去 level * TREE_INDENT // 这样无论对齐方式如何(left/center/right),都以实际图标为基准定位所有竖线 let baseX = treeImageX - level * TREE_INDENT; // 逐层画竖线(仅当 level > 0 才需要祖先连线) const parentRowKeys = Array.isArray(row.parentRowKeys) ? row.parentRowKeys : []; if (level > 0) { // 祖先层(0..level-2):是否继续取决于该层“下一层链路节点”是否为最后子项 // 例如当前节点 0-2-1: // - 对 i=0(根 0 对应竖线),应看其下一层链路节点 0-2 是否为最后子项。 // 若 0-2 是最后子项(没有 0-3),则 i=0 的竖线在本行不应出现。 for (let i = 0; i < level - 1; i += 1) { const nextKey = parentRowKeys[i + 1]; const nextRow = nextKey ? this.ctx.database.getRowForRowKey(nextKey) || {} : {}; const nextIsLast = !!nextRow.isLastChild; if (nextIsLast) continue; // 以当前树图标 X 为基准向左回推 const vx = Math.round(treeImageX - (level - i) * TREE_INDENT + TREE_ICON_SIZE / 2); this.ctx.paint.drawLine([vx, this.drawY, vx, this.drawY + this.visibleHeight], { borderColor: TREE_LINE_COLOR, borderWidth: 1, lineDash: [4, 4], lineDashOffset: 0, }); } // 父层(level-1):无论父是不是末尾,都需要连接当前节点形成 L 型 const vxParent = Math.round(treeImageX - TREE_INDENT + TREE_ICON_SIZE / 2); const toCenter = !!row.isLastChild; const y2 = toCenter ? iconCenterY : this.drawY + this.visibleHeight; this.ctx.paint.drawLine([vxParent, this.drawY, vxParent, y2], { borderColor: TREE_LINE_COLOR, borderWidth: 1, lineDash: [4, 4], lineDashOffset: 0, }); // 当前层的横线:从当前层竖线到图标中心 const currVX = Math.round(baseX + (level - 1) * TREE_INDENT + TREE_ICON_SIZE / 2); this.ctx.paint.drawLine([currVX, iconCenterY, iconCenterX, iconCenterY], { borderColor: TREE_LINE_COLOR, borderWidth: 1, lineDash: [4, 4], lineDashOffset: 0, }); } // 1) 父节点行:在图标正下画一段短竖线(展开时绘制,符合视觉预期) if (row.hasChildren && row.expand) { const shortTop = treeImageY + treeImageHeight; const shortBottom = this.drawY + this.visibleHeight; this.ctx.paint.drawLine([iconCenterX, shortTop, iconCenterX, shortBottom], { borderColor: TREE_LINE_COLOR, borderWidth: 1, lineDash: [4, 4], lineDashOffset: 0, }); } } updateContainer() { const { BODY_BG_COLOR, EDIT_BG_COLOR, BODY_CELL_STYLE_METHOD, FOOTER_CELL_STYLE_METHOD, READONLY_TEXT_COLOR, BODY_TEXT_COLOR, FOOTER_TEXT_COLOR, FOOTER_BG_COLOR, HIGHLIGHT_SELECTED_ROW, HIGHLIGHT_SELECTED_ROW_COLOR, HIGHLIGHT_HOVER_ROW, HIGHLIGHT_HOVER_ROW_COLOR, STRIPE, STRIPE_COLOR, FINDER_CELL_BG_COLOR, } = this.ctx.config; if (this.cellType === 'footer') { let bgColor = FOOTER_BG_COLOR; let textColor = FOOTER_TEXT_COLOR; if (typeof FOOTER_CELL_STYLE_METHOD === 'function') { const footerCellStyleMethod = FOOTER_CELL_STYLE_METHOD; const { backgroundColor, color, font } = footerCellStyleMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }) || {}; if (backgroundColor) { bgColor = backgroundColor; } // 文字颜色 if (color) { textColor = color; } if (font) { this.drawTextFont = font; } } // 合计底部背景色 this.drawCellSkyBgColor = 'transparent'; this.drawCellBgColor = bgColor; this.drawTextColor = textColor; return; } // 高亮行,在背景色上加一层颜色 let drawCellSkyBgColor = 'transparent'; // 高亮行 const hoverCell = this.ctx.hoverCell; const currentCell = this.ctx.currentCell; // 合并单元格 let minY = this.rowIndex; let maxY = this.rowIndex; if (this.rowspan !== 1 && (HIGHLIGHT_HOVER_ROW || HIGHLIGHT_SELECTED_ROW)) { const spanInfo = this.getSpanInfo(); const { yArr } = spanInfo; minY = yArr[0]; maxY = yArr[1]; } if (HIGHLIGHT_HOVER_ROW && hoverCell && !this.ctx.dragRowIng) { if (hoverCell.rowKey === this.rowKey) { drawCellSkyBgColor = HIGHLIGHT_HOVER_ROW_COLOR; } if (hoverCell.rowIndex >= minY && hoverCell.rowIndex <= maxY) { drawCellSkyBgColor = HIGHLIGHT_HOVER_ROW_COLOR; } } if (HIGHLIGHT_SELECTED_ROW && currentCell && !this.ctx.dragRowIng) { if (currentCell.rowKey === this.rowKey) { drawCellSkyBgColor = HIGHLIGHT_SELECTED_ROW_COLOR; } if (currentCell.rowIndex >= minY && currentCell.rowIndex <= maxY) { drawCellSkyBgColor = HIGHLIGHT_SELECTED_ROW_COLOR; } } this.drawCellSkyBgColor = drawCellSkyBgColor; // 恢复默认背景色 let bgColor = BODY_BG_COLOR; let textColor = BODY_TEXT_COLOR; // 只读 if (!this.ctx.database.getReadonly(this.rowKey, this.key)) { bgColor = EDIT_BG_COLOR; textColor = READONLY_TEXT_COLOR; } // 斑马纹,编辑背景色下会失效 if (STRIPE) { if (this.rowIndex % 2) { bgColor = STRIPE_COLOR; } else { bgColor = BODY_BG_COLOR; } } if (typeof BODY_CELL_STYLE_METHOD === 'function') { const cellStyleMethod = BODY_CELL_STYLE_METHOD; const { backgroundColor, color, font } = cellStyleMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, isHasChanged: this.isHasChanged, value: this.getValue(), }) || {}; if (backgroundColor) { bgColor = backgroundColor; } // 文字颜色 if (color) { textColor = color; } if (font) { this.drawTextFont = font; } } // 高亮查找结果 const { rowIndex, colIndex, type } = this.ctx.finderBar; if (rowIndex === this.rowIndex && colIndex === this.colIndex && type === 'body') { bgColor = FINDER_CELL_BG_COLOR; } this.drawCellBgColor = bgColor; this.drawTextColor = textColor; } updateSelection() { const { visibleWidth, visibleHeight, rowspan, colspan, cellType, type, rowIndex, rowKey } = this; //合并的选框不显示 if (rowspan === 0 || colspan === 0) { return; } // 合计不显示 if (cellType === 'footer') { return; } if (!['index-selection', 'selection', 'selection-tree', 'tree-selection'].includes(type)) { return; } const selectable = this.ctx.database.getRowSelectable(rowKey); const { CHECKBOX_SIZE = 0, CELL_PADDING } = this.ctx.config; let drawX = this.drawX + CELL_PADDING; if (this.align === 'center' || this.align === 'right') { drawX = this.drawX + (visibleWidth - CHECKBOX_SIZE) / 2; } let iconX = drawX; let iconY = this.drawY + (visibleHeight - CHECKBOX_SIZE) / 2; // 对于 selection-tree 类型,checkbox 应该居中显示 if (type === 'selection-tree') { } else if (type === 'tree-selection') { // 更新选择器的位置 const { TREE_INDENT = 16, TREE_ICON_SIZE } = this.ctx.config; const row = this.ctx.database.getRowForRowKey(rowKey); const { level = 0 } = row || {}; const iconOffsetX = level * TREE_INDENT; iconX = drawX + TREE_ICON_SIZE + iconOffsetX; // 树形图标右侧 + 间距 } let checkboxImage = this.ctx.icons.get('checkbox-uncheck'); let checkboxName = 'checkbox-uncheck'; if (type === 'selection-tree' || type === 'tree-selection') { // 树形选择逻辑 const treeState = this.ctx.database.getTreeSelectionState(rowKey); if (treeState.indeterminate && selectable) { checkboxImage = this.ctx.icons.get('checkbox-indeterminate'); checkboxName = 'checkbox-indeterminate'; } else if (treeState.checked && selectable) { checkboxImage = this.ctx.icons.get('checkbox-check'); checkboxName = 'checkbox-check'; } else if (!treeState.checked && selectable) { checkboxImage = this.ctx.icons.get('checkbox-uncheck'); checkboxName = 'checkbox-uncheck'; } else { checkboxImage = this.ctx.icons.get('checkbox-disabled'); checkboxName = 'checkbox-disabled'; } } else { // 普通选择逻辑 const check = this.ctx.database.getRowSelection(rowKey); if (check && selectable) { checkboxImage = this.ctx.icons.get('checkbox-check'); checkboxName = 'checkbox-check'; } else if (check && !selectable) { checkboxImage = this.ctx.icons.get('checkbox-check-disabled'); checkboxName = 'checkbox-check-disabled'; } else if (!check && selectable) { checkboxImage = this.ctx.icons.get('checkbox-uncheck'); checkboxName = 'checkbox-uncheck'; } else { checkboxImage = this.ctx.icons.get('checkbox-disabled'); checkboxName = 'checkbox-disabled'; } } const dragImage = this.getImage('drag'); const drawDragImageWidth = dragImage ? dragImage.width : 0; iconX += drawDragImageWidth; // 判断是否溢出格子 if (iconX + CHECKBOX_SIZE + CELL_PADDING > this.drawX + this.visibleWidth) { return; } if (iconY + CHECKBOX_SIZE + CELL_PADDING > this.drawY + this.visibleHeight) { return; } const selectionImage = new CellImage(checkboxName, iconX, iconY, CHECKBOX_SIZE, CHECKBOX_SIZE, checkboxImage); if (type === 'index-selection') { selectionImage.setVisible(false); if ((this.ctx.hoverCell && this.ctx.hoverCell.rowIndex === rowIndex) || ['checkbox-disabled', 'checkbox-check'].includes(checkboxName)) { selectionImage.setVisible(true); } let minY = this.rowIndex; let maxY = this.rowIndex; const hoverCell = this.ctx.hoverCell; const spanInfo = this.getSpanInfo(); const { yArr } = spanInfo; minY = yArr[0]; maxY = yArr[1]; if (hoverCell && hoverCell.rowIndex >= minY && hoverCell.rowIndex <= maxY) { selectionImage.setVisible(true); } } this.setImage('selection', selectionImage); } updateHoverIcon() { const readonly = this.ctx.database.getReadonly(this.rowKey, this.key); if (readonly) { return; } const { BODY_CELL_HOVER_ICON_METHOD, CELL_HOVER_ICON_SIZE, CELL_PADDING, ENABLE_MERGE_CELL_LINK } = this.ctx.config; if (typeof BODY_CELL_HOVER_ICON_METHOD === 'function') { const hoverIconMethod = BODY_CELL_HOVER_ICON_METHOD; const hoverIconName = hoverIconMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); // 可以动态改变hoverIconName if (hoverIconName !== undefined) { this.hoverIconName = hoverIconName; } } // 永远放在右边 const { hoverCell } = this.ctx; if (this.hoverIconName && !this.ctx.editing && !this.ctx.dragRowIng && hoverCell) { let _x = 0; let _y = 0; if (hoverCell.rowKey === this.rowKey) { _x = this.drawX + this.width - CELL_HOVER_ICON_SIZE - CELL_PADDING; _y = this.drawY + (this.height - CELL_HOVER_ICON_SIZE) / 2; } // 合并单元格 if (this.rowspan !== 1 && ENABLE_MERGE_CELL_LINK) { const spanInfo = this.getSpanInfo(); const { yArr } = spanInfo; const minY = yArr[0]; const maxY = yArr[1]; if (hoverCell.rowIndex >= minY && hoverCell.rowIndex <= maxY) { const { width, height, offsetTop, offsetLeft } = spanInfo; _x = this.drawX - offsetLeft + width - CELL_HOVER_ICON_SIZE - CELL_PADDING; _y = this.drawY - offsetTop + (height - CELL_HOVER_ICON_SIZE) / 2; } } const drawImageSource = this.ctx.icons.get(this.hoverIconName); const hoverImage = new CellImage(this.hoverIconName, _x, _y, CELL_HOVER_ICON_SIZE, CELL_HOVER_ICON_SIZE, drawImageSource); if (this.rowspan === 0 || this.colspan === 0) { hoverImage.setVisible(false); } this.setImage('hover', hoverImage); } } /** * 获取自动高度 * @returns */ getAutoHeight() { if (this.cellType !== 'body') { return 0; } if (!this.autoRowHeight) { return 0; } if (this.rowspan === 0) { return 0; } // 如果有渲染函数,使用渲染函数计算高度 if (this.render) { const renderHeight = this.ctx.database.getOverlayerAutoHeight(this.rowIndex, this.colIndex); if (this.rowspan > 1) { // 如果计算高度小于可见高度,返回0,表示不显示 if (renderHeight < this.visibleHeight) { return 0; } return Math.round(renderHeight - (this.visibleHeight - this.height)); } return Math.round(renderHeight); } if (!(this.displayText && typeof this.displayText === 'string')) { return 0; } const { BODY_FONT, CELL_PADDING, CELL_LINE_HEIGHT } = this.ctx.config; const cacheTextKey = `${this.displayText}_${this.drawTextWidth}_${this.drawTextFont}`; const calculatedHeight = this.ctx.paint.calculateTextHeight(this.displayText, this.drawTextWidth, { font: this.drawTextFont || BODY_FONT, padding: CELL_PADDING, align: this.align, verticalAlign: this.verticalAlign, color: this.drawTextColor, autoRowHeight: this.autoRowHeight, lineHeight: CELL_LINE_HEIGHT, maxLineClamp: this.maxLineClamp, cacheTextKey, }); // 合并单元格处理 if (this.rowspan > 1) { // 如果计算高度小于可见高度,返回0,表示不显示 if (calculatedHeight < this.visibleHeight) { return 0; } return Math.round(calculatedHeight - (this.visibleHeight - this.height)); } // 转成整数 return Math.round(calculatedHeight); } // 过去跨度配置 getSpanInfo() { return this.ctx.database.getSpanInfo(this); } /** * 获取显示文本 * @returns */ getDisplayText() { if (this.cellType === 'footer') { // 插槽不显示文本 if (this.renderFooter && this.renderFooterType === 'default') { return ''; } if (this.text === null || this.text === undefined) { return ''; } return this.text; } else { // cellType === "body" // 被跨度单元格 if (this.rowspan === 0 || this.colspan === 0) { return ''; } // 自定义渲染为 default 且非混合渲染时不显示 canvas 文本 if (this.render && this.renderType === 'default' && !this.mixedRender) { return ''; } const selectionImage = this.getImage('selection'); if (this.type === 'index-selection' && selectionImage && selectionImage.visible) { return ''; } if (this.text === null || this.text === undefined) { return ''; } return `${this.text}`; } } /** * 获取文本 * @returns */ getText() { if (this.cellType === 'footer') { if (typeof this.formatterFooter === 'function') { const _text = this.formatterFooter({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.row[this.key], }); return _text; } return this.row[this.key]; } // formatter优先等级比较高 if (typeof this.formatter === 'function') { const _text = this.formatter({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); return _text; } const { BODY_CELL_FORMATTER_METHOD } = this.ctx.config; if (typeof BODY_CELL_FORMATTER_METHOD === 'function') { const formatterMethod = BODY_CELL_FORMATTER_METHOD; const _text = formatterMethod({ row: this.row, rowIndex: this.rowIndex, colIndex: this.colIndex, column: this.column, value: this.getValue(), }); return _text; } if (['index-selection', 'index'].includes(this.type)) { const str = `${this.rowIndex + 1}`; // 索引 return str; // 索引 } this.value = this.ctx.database.getItemValue(this.rowKey, this.key); return this.value; } getValue() { return this.ctx.database.getItemValue(this.rowKey, this.key); } // 拓展格子可设置数据 setValue(value) { this.ctx.setItemValueByEditor(this.rowKey, this.key, value); } /** * 获取样式 */ getOverlayerViewsStyle() { let left = this.drawX - this.ctx.fixedLeftWidth; let top = this.drawY - this.ctx.body.y; // 固定列 if (this.fixed === 'left') { left = this.drawX; } else if (this.fixed === 'right') { left = this.drawX - (this.ctx.stageWidth - this.ctx.fixedRightWidth); } // 合计 if (this.cellType === 'footer') { if (this.ctx.config.FOOTER_FIXED) { top = this.drawY - this.ctx.footer.y; } } // 定位到居中 if (this.autoRowHeight && this.render && this.verticalAlign === 'middle') { const renderHeight = this.ctx.database.getOverlayerAutoHeight(this.rowIndex, this.colIndex); if (renderHeight < this.visibleHeight && renderHeight > 0) { const remainTop = (this.visibleHeight - renderHeight) / 2; top = top + remainTop; } } // 防止覆盖层叠加显示,直接隐藏后显示 if (this.autoRowHeight && this.ctx.database.getOverlayerAutoHeight(this.rowIndex, this.colIndex) === 0) { left = -99999; top = -99999; } let autoStyle = {}; if (this.rowspan === 0) { autoStyle = { display: 'none', }; } return { position: 'absolute', overflow: 'hidden', left: `${Math.round(left - 1)}px`, top: `${Math.round(top - 1)}px`, width: `${this.visibleWidth}px`, height: this.autoRowHeight && this.renderType === 'default' ? 'auto' : `${this.visibleHeight}px`, // height: `${this.visibleHeight}px`, // minHeight: `${this.visibleHeight}px`, pointerEvents: 'initial', userSelect: 'none', ...autoStyle, }; } drawContainer() { const { paint, config: { BORDER_COLOR, BORDER }, } = this.ctx; const { drawX, drawY } = this; const drawFullBorder = shouldDrawFullCellBorder(BORDER); paint.drawRect(drawX, drawY, this.visibleWidth, this.visibleHeight, { borderColor: drawFullBorder ? BORDER_COLOR : 'transparent', fillColor: this.drawCellBgColor, }); // 列合并单元格 paint.drawRect(drawX, drawY, this.width, this.height, { borderColor: 'transparent', fillColor: this.drawCellSkyBgColor, }); } drawHorizontalBorder() { const { config: { BORDER_COLOR, BORDER }, } = this.ctx; if (shouldDrawFullCellBorder(BORDER) || !shouldDrawInternalHorizontalBorder(BORDER)) { return; } const { drawX, drawY } = this; this.ctx.paint.drawLine([drawX, drawY + this.visibleHeight, drawX + this.visibleWidth, drawY + this.visibleHeight], { borderColor: BORDER_COLOR, fillColor: BORDER_COLOR, borderWidth: 1, lineCap: 'round', lineJoin: 'round', }); } drawAutofillPiont() { if (this.cellType === 'footer') { return; } const { SELECT_BORDER_COLOR, ENABLE_AUTOFILL, ENABLE_SELECTOR, AUTOFILL_POINT_BORDER_COLOR } = this.ctx.config; if (!ENABLE_SELECTOR) { return; } if (!ENABLE_AUTOFILL) { return; } if (this.ctx.editing) { return; } const show = true; const { xArr, yArr } = this.ctx.selector; const maxX = xArr[1]; const maxY = yArr[1]; const { colIndex, rowIndex, drawX, drawY } = this; // 绘制自动填充点 if (show && colIndex === maxX && rowIndex === maxY) { const isOffset = colIndex === this.ctx.maxColIndex || rowIndex === this.ctx.maxRowIndex || colIndex === this.ctx.lastCenterColIndex; const offset = isOffset ? 6 : 4; this.ctx.paint.drawRect(drawX + this.width - offset, drawY + this.height - offset, 6, 6, { borderColor: AUTOFILL_POINT_BORDER_COLOR, fillColor: SELECT_BORDER_COLOR, }); } } draw() { // 树连线(需在文本之前绘制,避免覆盖图标但不遮挡文本) this.drawTreeLine(); // 文字与图标 this.drawText(); this.drawImage(); this.drawSelector(); this.drawAutofillPiont(); this.drawErrorTip(); } /** * 根据列的索引获取列的宽度 * @param {Number} colIndex */ getWidthByColIndexColSpan(colIndex, colSpan) { if (colSpan === 0) { return 0; } let width = 0; for (let i = colIndex; i < colIndex + colSpan; i++) { const cellHeader = this.ctx.header.leafCellHeaders[i]; width += cellHeader.width; } return width; } drawText() { const { CELL_PADDING, BODY_FONT, PLACEHOLDER_COLOR, CELL_LINE_HEIGHT } = this.ctx.config; const { placeholder } = this.column; let text = this.displayText; let color = this.drawTextColor; // 更改颜色 const isReadonly = this.ctx.database.getReadonly(this.rowKey, this.key); if (!isReadonly && placeholder && ['', null, undefined].includes(this.text) && this.cellType === 'body' && !(this.rowspan === 0 || this.colspan === 0)) { text = placeholder; color = PLACEHOLDER_COLOR; } if (['', null, undefined].includes(text)) {