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.
486 lines • 18.8 kB
JavaScript
import { getMaxRow, calCrossSpan, toLeaf, sortFixed, throttle } from './util';
import CellHeader from './CellHeader';
export default class Header {
constructor(ctx) {
Object.defineProperty(this, "ctx", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
}); // 上下文
Object.defineProperty(this, "x", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // x坐标
Object.defineProperty(this, "y", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // y坐标
Object.defineProperty(this, "width", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // 宽度
Object.defineProperty(this, "height", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // 高度
Object.defineProperty(this, "resizeTarget", {
enumerable: true,
configurable: true,
writable: true,
value: null
}); //调整表头
Object.defineProperty(this, "resizeNum", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // 调整列的数量
Object.defineProperty(this, "isResizing", {
enumerable: true,
configurable: true,
writable: true,
value: false
}); // 是否移动中
Object.defineProperty(this, "clientX", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // 鼠标按下时的x轴位置
Object.defineProperty(this, "resizeDiff", {
enumerable: true,
configurable: true,
writable: true,
value: 0
}); // 是否移动中
Object.defineProperty(this, "columnIndex", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "isMouseDown", {
enumerable: true,
configurable: true,
writable: true,
value: false
}); // 是否按下
Object.defineProperty(this, "columns", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "visibleLeafColumns", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "visibleHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "visibleWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "allCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "leafCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "renderLeafCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "fixedLeftCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "centerCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "fixedRightCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "renderCenterCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "renderFixedCellHeaders", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.ctx = ctx;
// 监听表头重置,窗口变化
this.ctx.on('resetHeader', throttle(() => {
this.init();
this.ctx.emit('draw');
}, 100));
this.init();
// 初始化调整列大小ENABLE_RESIZE_COLUMN
this.initResizeColumn();
}
init() {
const { config: { HEADER_HEIGHT, SCROLLER_TRACK_SIZE }, } = this.ctx;
const columns = this.ctx.database.getColumns();
this.columns = columns;
this.allCellHeaders = [];
this.leafCellHeaders = [];
this.fixedLeftCellHeaders = [];
this.fixedRightCellHeaders = [];
this.centerCellHeaders = [];
const maxHeaderRow = getMaxRow(columns);
const leafColumns = toLeaf(columns);
this.height = HEADER_HEIGHT * maxHeaderRow;
this.width = leafColumns.reduce((sum, _item) => sum + (_item?.width || 100), 0);
this.visibleHeight = this.height;
const spanColumns = sortFixed(calCrossSpan(columns, maxHeaderRow));
this.columnIndex = 0;
this.resizeNum = 0; // 可调整调整列数量
this.render(spanColumns, 0);
this.ctx.database.updateColIndexKeyMap(this.leafCellHeaders);
const containerElement = this.ctx.containerElement.getBoundingClientRect();
// 如果有可调整列,宽度等于容器宽度
if (this.resizeNum > 0) {
// this.ctx.canvasElement.width = containerElement.width;
this.ctx.stageWidth = Math.floor(containerElement.width);
}
else {
// this.ctx.canvasElement.width = this.width + SCROLLER_TRACK_SIZE - 1;
this.ctx.stageWidth = Math.floor(this.width + SCROLLER_TRACK_SIZE);
}
this.ctx.stageElement.style.width = this.ctx.stageWidth - 0.5 + 'px';
this.visibleWidth = this.ctx.stageWidth - SCROLLER_TRACK_SIZE;
// 如果表头宽度小于可视宽度,平均分配
const overWidth = this.visibleWidth - this.width;
if (this.resizeNum && overWidth > 0) {
const diff = Math.floor((overWidth / this.resizeNum) * 100) / 100;
this.resizeAllColumn(diff);
}
const leafLeftCellHeaders = this.fixedLeftCellHeaders.filter((item) => !item.hasChildren);
this.ctx.fixedLeftWidth = leafLeftCellHeaders.reduce((sum, _item) => sum + _item.width, 0);
const leafRightCellHeaders = this.fixedRightCellHeaders.filter((item) => !item.hasChildren);
this.ctx.fixedRightWidth = leafRightCellHeaders.reduce((sum, _item) => sum + _item.width, SCROLLER_TRACK_SIZE);
// 更新最大列索引
this.ctx.maxColIndex = this.leafCellHeaders.length - 1;
this.ctx.header.x = this.x;
this.ctx.header.y = this.y;
this.ctx.header.width = this.width;
this.ctx.header.height = this.height;
this.ctx.header.visibleWidth = this.visibleWidth;
this.ctx.header.visibleHeight = this.visibleHeight;
}
// 调整表头的宽度
initResizeColumn() {
const { config: { ENABLE_RESIZE_COLUMN }, } = this.ctx;
if (!ENABLE_RESIZE_COLUMN) {
return;
}
this.ctx.on('mousedown', (e) => {
if (!this.ctx.isTarget(e)) {
return;
}
this.clientX = e.clientX;
if (this.resizeTarget) {
this.isResizing = true;
this.ctx.columnResizing = true;
}
else {
this.isResizing = false;
}
this.isMouseDown = true;
});
this.ctx.on('mouseup', () => {
this.isMouseDown = false;
// 清空
if (this.resizeDiff !== 0 && this.resizeTarget) {
// 调整宽度
this.resizeColumn(this.resizeTarget, this.resizeDiff);
}
this.resizeTarget = null;
this.isResizing = false;
this.ctx.columnResizing = false;
this.clientX = 0;
});
this.ctx.on('mousemove', (e) => {
// 编辑中不触发mousemove
if (this.ctx.editing)
return;
const { stageWidth, config: { RESIZE_COLUMN_MIN_WIDTH }, } = this.ctx;
// 鼠标移动
if (this.isResizing && this.resizeTarget) {
const resizeTargetWidth = this.resizeTarget.width;
let diff = e.clientX - this.clientX;
if (diff + resizeTargetWidth < RESIZE_COLUMN_MIN_WIDTH) {
diff = -(resizeTargetWidth - RESIZE_COLUMN_MIN_WIDTH);
}
this.resizeDiff = diff;
this.ctx.emit('draw');
}
else {
this.resizeTarget = null;
// 按下时不改变样式,有可能是多选表头
if (this.isMouseDown) {
return;
}
// 鼠标在表头上,边界处理
if (e.offsetX < 0 || e.offsetX > this.visibleWidth) {
if (this.ctx.stageElement.style.cursor === 'col-resize') {
this.ctx.stageElement.style.cursor = 'default';
}
return;
}
// 恢复默认样式
if (this.ctx.stageElement.style.cursor === 'col-resize') {
this.ctx.stageElement.style.cursor = 'default';
}
// 渲染的表头
const renderAllCellHeaders = [...this.renderFixedCellHeaders, ...this.renderCenterCellHeaders];
for (const col of renderAllCellHeaders) {
const { offsetX, offsetY } = this.ctx.getOffset(e);
const x = offsetX;
const drawX = col.getDrawX();
if (x > drawX + col.width - 5 &&
x < drawX + col.width + 4 &&
x < stageWidth - 4 && // 视窗中最后一列不允许调整宽
col.colspan <= 1 // 父级表头不触发
) {
// 在表头内
if (this.ctx.isTarget(e) && offsetY <= this.height) {
this.ctx.stageElement.style.cursor = 'col-resize';
this.resizeTarget = col;
}
}
}
}
});
}
resizeColumn(cell, diff) {
const setWidth = (columns) => {
columns.forEach((column) => {
if (column.children && column.children.length > 0) {
setWidth(column.children);
}
if (column.key === cell.key) {
const width = column.width || 100;
column.width = width + diff;
}
});
};
setWidth(this.columns);
this.ctx.database.setColumns(this.columns);
this.init();
this.ctx.emit('draw');
let overDiff = 0;
// 如果表头宽度小于可视宽度,平均分配
if (this.width < this.visibleWidth) {
const overWidth = this.visibleWidth - this.width;
overDiff = Math.floor((overWidth / this.resizeNum) * 100) / 100;
this.resizeAllColumn(overDiff);
this.ctx.emit('draw');
}
this.ctx.emit('resizeColumnChange', {
colIndex: cell.colIndex,
key: cell.key,
oldWidth: cell.width,
width: cell.width + diff + overDiff,
column: cell.column,
columns: this.columns,
});
}
resizeAllColumn(fellWidth) {
if (fellWidth === 0)
return;
const widthMap = new Map();
// 存需要更改的宽度
let isResize = true;
for (const col of this.allCellHeaders) {
if (col.widthFillDisable) {
// 不允许调整宽度
widthMap.set(col.key, col.width);
}
else {
const width = col.width + fellWidth * col.colspan;
widthMap.set(col.key, width);
if (width < this.ctx.config.RESIZE_COLUMN_MIN_WIDTH) {
isResize = false;
}
}
}
// 如果有小于RESIZE_COLUMN_MIN_WIDTH的列,不允许调整
if (!isResize) {
return;
}
// 递归更改宽度
const uptateWidth = (columns) => {
columns.forEach((column) => {
if (widthMap.has(column.key)) {
column.width = widthMap.get(column.key);
}
if (column.children && column.children.length > 0) {
uptateWidth(column.children);
}
});
};
uptateWidth(this.columns);
this.ctx.database.setColumns(this.columns);
this.init();
// this.ctx.emit("draw");
}
render(arr, originX) {
const len = arr.length;
let everyOffsetX = originX;
const { HEADER_HEIGHT = 0 } = this.ctx.config;
for (let i = 0; i < len; i++) {
const item = arr[i];
const height = HEADER_HEIGHT * (item.rowspan || 0);
const y = HEADER_HEIGHT * (item.level || 0);
let width = item.width || 100; // 读取映射宽度
if (item.children) {
// 父级表头的宽度是叶子节点表头的宽度总和
const _arr = toLeaf(item.children);
width = _arr.reduce((sum, _item) => sum + (_item?.width || 100), 0);
}
const cellHeader = new CellHeader(this.ctx, this.columnIndex, everyOffsetX, y, width, height, item);
// 设置表头
this.ctx.database.setHeader(item.key, cellHeader);
this.allCellHeaders.push(cellHeader);
if (!item.children) {
this.leafCellHeaders.push(cellHeader);
if (!cellHeader.column.widthFillDisable) {
this.resizeNum++;
}
}
if (item.fixed === 'left') {
this.fixedLeftCellHeaders.push(cellHeader);
}
else if (item.fixed === 'right') {
this.fixedRightCellHeaders.push(cellHeader);
}
else {
this.centerCellHeaders.push(cellHeader);
}
!item.children && this.columnIndex++;
item.children && this.render(item.children, everyOffsetX);
everyOffsetX += width;
}
}
drawTipLine() {
if (this.isResizing && this.resizeTarget) {
const { stageHeight, config: { RESIZE_COLUMN_LINE_COLOR }, } = this.ctx;
const resizeTargetDrawX = this.resizeTarget.getDrawX();
const resizeTargetWidth = this.resizeTarget.width;
const x = resizeTargetDrawX + resizeTargetWidth + this.resizeDiff - 0.5;
const poins = [x - 0.5, 0, x - 0.5, stageHeight];
this.ctx.paint.drawLine(poins, {
borderColor: RESIZE_COLUMN_LINE_COLOR,
borderWidth: 1,
});
}
}
drawFiexShadow() {
const { fixedLeftWidth, fixedRightWidth, scrollX, header, stageWidth, config: { HEADER_BG_COLOR, SCROLLER_TRACK_SIZE }, } = this.ctx;
if (scrollX > 0 && fixedLeftWidth !== 0) {
this.ctx.paint.drawShadow(this.x, this.y, fixedLeftWidth, this.height, {
fillColor: HEADER_BG_COLOR,
side: 'right',
shadowWidth: 4,
colorStart: 'rgba(0,0,0,0.1)',
colorEnd: 'rgba(0,0,0,0)',
});
}
// 右边阴影
if (scrollX < Math.floor(header.width - stageWidth - 1) && fixedRightWidth !== SCROLLER_TRACK_SIZE) {
const x = header.width - (this.x + this.width) + stageWidth - fixedRightWidth;
this.ctx.paint.drawShadow(x + 1, this.y, fixedRightWidth, this.height, {
fillColor: HEADER_BG_COLOR,
side: 'left',
shadowWidth: 4,
colorStart: 'rgba(0,0,0,0)',
colorEnd: 'rgba(0,0,0,0.1)',
});
}
}
update() {
const renderLeafCellHeaders = [];
const renderCenterCellHeaders = [];
const renderFixedCellHeaders = [];
this.centerCellHeaders.forEach((item) => {
if (item.isHorizontalVisible() && item.isVerticalVisible()) {
renderCenterCellHeaders.push(item);
if (!item.hasChildren) {
renderLeafCellHeaders.push(item);
}
}
});
this.fixedLeftCellHeaders.forEach((item) => {
renderFixedCellHeaders.push(item);
if (!item.hasChildren) {
renderLeafCellHeaders.push(item);
}
});
this.fixedRightCellHeaders.forEach((item) => {
renderFixedCellHeaders.push(item);
if (!item.hasChildren) {
renderLeafCellHeaders.push(item);
}
});
this.renderCenterCellHeaders = renderCenterCellHeaders;
this.renderFixedCellHeaders = renderFixedCellHeaders;
this.renderLeafCellHeaders = renderLeafCellHeaders.sort((a, b) => a.x - b.x);
this.visibleLeafColumns = this.renderLeafCellHeaders.map((item) => item.column);
this.ctx.header.visibleLeafColumns = this.visibleLeafColumns;
this.ctx.header.leafCellHeaders = this.leafCellHeaders;
this.ctx.header.renderLeafCellHeaders = this.renderLeafCellHeaders;
this.ctx.header.renderCellHeaders = this.renderFixedCellHeaders.concat(this.renderCenterCellHeaders);
}
draw() {
this.renderCenterCellHeaders.forEach((item) => {
item.update();
item.draw();
});
this.drawFiexShadow();
this.renderFixedCellHeaders.forEach((item) => {
item.update();
item.draw();
});
this.drawTipLine();
}
}
//# sourceMappingURL=Header.js.map