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,378 lines (1,377 loc) • 57.7 kB
JavaScript
import BaseCell from './BaseCell';
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, "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
});
// 画tree图标
Object.defineProperty(this, "drawTreeImageX", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawTreeImageY", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawTreeImageWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawTreeImageHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawTreeImageName", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "drawTreeImageSource", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// 画selection图标
Object.defineProperty(this, "drawSelectionImageX", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawSelectionImageY", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawSelectionImageWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawSelectionImageHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawSelectionImageName", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "drawSelectionImageSource", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// 画hover图标
Object.defineProperty(this, "drawHoverImageX", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawHoverImageY", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawHoverImageWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawHoverImageHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "drawHoverImageName", {
enumerable: true,
configurable: true,
writable: true,
value: ''
});
Object.defineProperty(this, "drawHoverImageSource", {
enumerable: true,
configurable: true,
writable: true,
value: void 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'
});
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';
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.drawTextX = this.drawX;
this.drawTextY = this.drawY;
this.isHasChanged = this.ctx.database.isHasChangedData(this.rowKey, this.key);
this.updateSpan();
this.drawTextWidth = this.visibleWidth;
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();
}
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;
if (this.type === 'selection-tree') {
// 树形图标在左侧,checkbox 在树形图标右侧
iconX = iconOffsetX + this.drawSelectionImageX + this.drawSelectionImageWidth;
drawTextX = iconX + iconWidth - CELL_PADDING / 2;
}
else if (this.type === 'tree-selection') {
// 树形选择,两个图标宽度,文本已经有CELL_PADDING间距,/2看起来好看些
drawTextX = iconX + CHECKBOX_SIZE + iconWidth - CELL_PADDING / 2;
}
else {
// 普通tree,文本已经有CELL_PADDING间距,/2看起来好看些
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.drawTreeImageX = iconX;
this.drawTreeImageY = iconY;
this.drawTreeImageWidth = iconWidth;
this.drawTreeImageHeight = iconHeight;
if (icon) {
this.drawTreeImageName = iconName;
this.drawTreeImageSource = icon;
}
else {
this.drawTreeImageName = '';
this.drawTreeImageSource = undefined;
}
// 树连线仅在绘制阶段调用,避免在 update 阶段被清屏
}
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 iconCenterX = this.drawTreeImageX + this.drawTreeImageWidth / 2;
const iconCenterY = this.drawTreeImageY + this.drawTreeImageHeight / 2;
// 基于已计算的树图标位置反推基准点:当前图标左侧减去 level * TREE_INDENT
// 这样无论对齐方式如何(left/center/right),都以实际图标为基准定位所有竖线
let baseX = this.drawTreeImageX - 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(this.drawTreeImageX - (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(this.drawTreeImageX - 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 = this.drawTreeImageY + this.drawTreeImageHeight;
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) {
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) {
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';
}
}
// 判断是否溢出格子
if (iconX + CHECKBOX_SIZE + CELL_PADDING > this.drawX + this.visibleWidth) {
return;
}
if (iconY + CHECKBOX_SIZE + CELL_PADDING > this.drawY + this.visibleHeight) {
return;
}
if (type === 'index-selection') {
if ((this.ctx.hoverCell && this.ctx.hoverCell.rowIndex === rowIndex) ||
['checkbox-disabled', 'checkbox-check'].includes(checkboxName)) {
this.drawSelectionImageX = iconX;
this.drawSelectionImageY = iconY;
this.drawSelectionImageWidth = CHECKBOX_SIZE;
this.drawSelectionImageHeight = CHECKBOX_SIZE;
this.drawSelectionImageName = checkboxName;
this.drawSelectionImageSource = checkboxImage;
}
}
else {
this.drawSelectionImageX = iconX;
this.drawSelectionImageY = iconY;
this.drawSelectionImageWidth = CHECKBOX_SIZE;
this.drawSelectionImageHeight = CHECKBOX_SIZE;
this.drawSelectionImageName = checkboxName;
this.drawSelectionImageSource = checkboxImage;
}
}
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 && 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);
this.drawHoverImageX = _x;
this.drawHoverImageY = _y;
this.drawHoverImageWidth = CELL_HOVER_ICON_SIZE;
this.drawHoverImageHeight = CELL_HOVER_ICON_SIZE;
this.drawHoverImageName = this.hoverIconName;
this.drawHoverImageSource = drawImageSource;
}
}
/**
* 获取自动高度
* @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) {
return '';
}
if (this.text === null || this.text === undefined) {
return '';
}
return this.text;
}
else {
// cellType === "body"
// 被跨度单元格
if (this.rowspan === 0 || this.colspan === 0) {
return '';
}
// 插槽不显示文本
if (this.render) {
return '';
}
//
if (this.type === 'index-selection' &&
((this.ctx.hoverCell && this.ctx.hoverCell.rowIndex === this.rowIndex) ||
['checkbox-disabled', 'checkbox-check'].includes(this.drawSelectionImageName))) {
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];
}
// cellType === "body"
// 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}px`;
let top = `${this.drawY - this.ctx.body.y}px`;
// 固定列
if (this.fixed === 'left') {
left = `${this.drawX}px`;
}
else if (this.fixed === 'right') {
left = `${this.drawX - (this.ctx.stageWidth - this.ctx.fixedRightWidth)}px`;
}
// 合计
if (this.cellType === 'footer') {
if (this.ctx.config.FOOTER_FIXED) {
top = `${this.drawY - this.ctx.footer.y}px`;
}
}
// 防止闪烁
if (this.autoRowHeight && this.ctx.database.getOverlayerAutoHeight(this.rowIndex, this.colIndex) === 0) {
left = '-99999px';
top = '-99999px';
}
return {
position: 'absolute',
overflow: 'hidden',
left,
top,
width: `${this.visibleWidth}px`,
height: this.autoRowHeight ? `auto` : `${this.visibleHeight}px`,
// height: `${this.visibleHeight}px`,
// minHeight: `${this.visibleHeight}px`,
pointerEvents: 'initial',
userSelect: 'none',
};
}
drawContainer() {
const { paint, config: { BORDER_COLOR, BORDER }, } = this.ctx;
const { drawX, drawY } = this;
paint.drawRect(drawX, drawY, this.visibleWidth, this.visibleHeight, {
borderColor: BORDER ? BORDER_COLOR : 'transparent',
fillColor: this.drawCellBgColor,
});
// 列合并单元格
paint.drawRect(drawX, drawY, this.width, this.height, {
borderColor: 'transparent',
fillColor: this.drawCellSkyBgColor,
});
if (!BORDER) {
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)) {
return false;
}
// 如果text 不是字符串,则转换为字符串
if (typeof text !== 'string') {
text = `${text}`;
}
const cacheTextKey = `${text}_${this.drawTextWidth}_${this.drawTextFont}`;
this.ellipsis = this.ctx.paint.drawText(text, this.drawTextX, this.drawTextY, this.drawTextWidth, this.drawTextHeight, {
font: this.drawTextFont || BODY_FONT,
padding: CELL_PADDING,
align: this.align,
verticalAlign: this.verticalAlign,
color,
autoRowHeight: this.autoRowHeight,
lineHeight: CELL_LINE_HEIGHT,
maxLineClamp: this.maxLineClamp,
cacheTextKey,
});
return this.ellipsis;
}
drawImage() {
if (this.drawSelectionImageSource) {
this.ctx.paint.drawImage(this.drawSelectionImageSource, this.drawSelectionImageX, this.drawSelectionImageY, this.drawSelectionImageWidth, this.drawSelectionImageHeight);
}
if (this.drawTreeImageSource) {
this.ctx.paint.drawImage(this.drawTreeImageSource, this.drawTreeImageX, this.drawTreeImageY, this.drawTreeImageWidth, this.drawTreeImageHeight);
}
if (this.drawHoverImageSource) {
// 绘制hover图标背景