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.

405 lines 15.4 kB
import Row from './Row'; export default class Body { constructor(ctx) { Object.defineProperty(this, "resizeTarget", { enumerable: true, configurable: true, writable: true, value: null }); //调整行大小的目标 Object.defineProperty(this, "isMouseDown", { enumerable: true, configurable: true, writable: true, value: false }); // 是否按下 Object.defineProperty(this, "resizeDiff", { enumerable: true, configurable: true, writable: true, value: 0 }); // 是否移动中 Object.defineProperty(this, "clientY", { enumerable: true, configurable: true, writable: true, value: 0 }); // 鼠标按下时的y轴位置 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 }); Object.defineProperty(this, "y", { enumerable: true, configurable: true, writable: true, value: 0 }); 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, "headIndex", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "tailIndex", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "isResizing", { enumerable: true, configurable: true, writable: true, value: false }); //是否正在调整大小 Object.defineProperty(this, "renderRows", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "visibleRows", { 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, "data", { enumerable: true, configurable: true, writable: true, value: [] }); this.ctx = ctx; this.init(); this.initResizeRow(); } init() { const { canvasElement, header, footer, database, config: { FOOTER_FIXED, SCROLLER_TRACK_SIZE = 0, HEIGHT, EMPTY_BODY_HEIGHT = 0, MAX_HEIGHT = 0, ENABLE_OFFSET_HEIGHT = 0, OFFSET_HEIGHT = 0, FOOTER_POSITION, }, } = this.ctx; if (!header.width) { return; } this.x = 0; if (FOOTER_POSITION === 'top' && FOOTER_FIXED) { this.y = header.height + footer.height; //更新body的y轴位置 } else { this.y = header.height; } const { data, sumHeight } = database.getData(); // 更新高度 this.height = sumHeight; // 更新数据 this.data = data; const { top } = canvasElement.getBoundingClientRect(); // 更新宽度 this.width = header.width; this.visibleWidth = this.ctx.stageWidth - SCROLLER_TRACK_SIZE; // 底部高度 const footerHeight = this.ctx.footer.height; if (!this.data.length && !HEIGHT) { this.height = EMPTY_BODY_HEIGHT; } else if (!this.data.length && HEIGHT) { // 如果有设置高度的情况,空数据时,高度也要保持为设置的高度 this.height = HEIGHT - header.height - footerHeight - SCROLLER_TRACK_SIZE; } const isEmpty = !this.data.length ? 'empty' : 'not-empty'; this.ctx.emit('emptyChange', { isEmpty, type: isEmpty, headerHeight: header.height, bodyHeight: this.height, footerHeight, width: this.width, height: !this.data.length ? EMPTY_BODY_HEIGHT + footerHeight : 0, }); let containerHeight = this.height + header.height + SCROLLER_TRACK_SIZE; // 如果有底部,加上底部高度 containerHeight += footerHeight; let stageHeight = 0; if (this.data.length && ENABLE_OFFSET_HEIGHT) { const windowInnerHeight = window.innerHeight; const visibleHeight = windowInnerHeight - top; stageHeight = visibleHeight - OFFSET_HEIGHT; if (stageHeight < 0) { stageHeight = 32; console.error('There is an error in the height calculation ENABLE_OFFSET_HEIGHT and OFFSET_HEIGHT are invalid'); } } else if (this.data.length && HEIGHT) { stageHeight = HEIGHT; } else if (this.data.length && MAX_HEIGHT && containerHeight > MAX_HEIGHT) { stageHeight = MAX_HEIGHT; } else { stageHeight = containerHeight; } // 更新窗口高度 if (stageHeight > 0) { // this.ctx.canvasElement.height = stageHeight; this.ctx.stageHeight = Math.floor(stageHeight); this.ctx.stageElement.style.height = `${this.ctx.stageHeight - 0.5}px`; } // 可视区高度 let _visibleHeight = this.ctx.stageHeight - header.height - SCROLLER_TRACK_SIZE; // 底部高度,如果是固定底部,可视区减上底部高度 if (FOOTER_FIXED) { this.visibleHeight = _visibleHeight - footerHeight; } else { this.visibleHeight = _visibleHeight; } this.ctx.body.x = this.x; this.ctx.body.y = this.y; this.ctx.body.width = this.width; this.ctx.body.height = this.height; this.ctx.body.visibleWidth = this.visibleWidth; this.ctx.body.visibleHeight = this.visibleHeight; this.ctx.body.data = data; const dpr = window.devicePixelRatio || 1; // 获取设备像素比 const canvasWidth = this.ctx.stageWidth * dpr; const canvasHeight = this.ctx.stageHeight * dpr; canvasElement.width = Math.floor(canvasWidth); canvasElement.height = Math.floor(canvasHeight); // 外层容器样式 this.ctx.canvasElement.setAttribute('style', ` height:${this.ctx.stageHeight}px;width:${this.ctx.stageWidth}px;`); this.ctx.paint.scale(dpr); } // 调整行的高度 initResizeRow() { const { config: { ENABLE_RESIZE_ROW }, } = this.ctx; if (!ENABLE_RESIZE_ROW) { return; } // 鼠标移动 this.ctx.on('mouseup', () => { this.isMouseDown = false; if (this.resizeDiff !== 0 && this.resizeTarget) { // 调整宽度 this.resizeRow(this.resizeTarget, this.resizeDiff); } this.resizeTarget = null; this.resizeDiff = 0; this.isResizing = false; //加个延时,修复拖动时,开始编辑的问题 setTimeout(() => { this.ctx.rowResizing = false; }, 0); this.clientY = 0; }); this.ctx.on('mousedown', (e) => { if (!this.ctx.isTarget()) { return; } this.clientY = e.clientY; if (this.resizeTarget) { this.isResizing = true; // 传递给上下文,防止其他事件触发,行调整大小时,不触发选择器 this.ctx.rowResizing = true; } else { this.isResizing = false; this.ctx.rowResizing = false; } this.isMouseDown = true; }); this.ctx.on('mousemove', (e) => { // 编辑中不触发mousemove if (this.ctx.editing) return; const { offsetY, offsetX } = this.ctx.getOffset(e); const y = offsetY; const x = offsetX; const clientY = e.clientY; const { stageHeight, scrollY, config: { RESIZE_ROW_MIN_HEIGHT = 0 }, } = this.ctx; if (this.isResizing && this.resizeTarget) { const resizeTargetHeight = this.resizeTarget.height; let diff = clientY - this.clientY; if (diff + resizeTargetHeight < RESIZE_ROW_MIN_HEIGHT) { diff = -(resizeTargetHeight - RESIZE_ROW_MIN_HEIGHT); } this.resizeDiff = diff; this.ctx.emit('draw'); } else { this.resizeTarget = null; // 按下时不改变样式,有可能是多选表头 if (this.isMouseDown) { return; } // 如果是拖动选择 if (this.ctx.stageElement.style.cursor === 'crosshair') { return; } if (this.ctx.stageElement.style.cursor === 'row-resize') { // 恢复默认样式 this.ctx.stageElement.style.cursor = 'default'; } for (let i = 0; i < this.renderRows.length; i++) { const row = this.renderRows[i]; const isYRange = y > row.y - scrollY + row.height - 1.5 && y < row.y - scrollY + row.height + 1.5 && y < stageHeight - 4; if (isYRange) { for (let j = 0; j < row.cells.length; j++) { const cell = row.cells[j]; if (x > cell.drawX + 10 && x < cell.drawX + cell.width - 10 && cell.rowspan === 1 //没有被合并的单元格 ) { this.ctx.stageElement.style.cursor = 'row-resize'; this.resizeTarget = row; } } } } } }); } resizeRow(row, diff) { const { rowIndex, height, rowKey, data } = row; this.ctx.database.setRowHeight(rowIndex, height + diff); this.init(); this.ctx.emit('draw'); this.ctx.emit('resizeRowChange', { rowIndex, oldHeight: height, height: height + diff, rowKey, row: data, data: this.data, }); } drawTipLine() { if (this.isResizing && this.resizeTarget) { const { stageWidth, scrollY, config: { RESIZE_ROW_LINE_COLOR }, } = this.ctx; const resizeTargetDrawY = this.resizeTarget.y - scrollY; const resizeTargetHeight = this.resizeTarget.height; const y = resizeTargetDrawY + resizeTargetHeight + this.resizeDiff - 0.5; const poins = [0, y - 0.5, stageWidth, y - 0.5]; this.ctx.paint.drawLine(poins, { borderColor: RESIZE_ROW_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)', }); } } binarySearch(list, value) { let start = 0; let end = list.length - 1; let tempIndex = -1; while (start <= end) { let midIndex = Math.floor((start + end) / 2); let midValue = list[midIndex].bottom; if (midValue === value) { return midIndex; } else if (midValue < value) { start = midIndex + 1; } else { tempIndex = midIndex; // 记录当前的 midIndex end = midIndex - 1; } } return tempIndex; } update() { this.init(); const { header, database, scrollY } = this.ctx; const offset = scrollY; const { data, positions } = database.getData(); // 更新最大行数 this.ctx.maxRowIndex = data.length - 1; const _headIndex = this.binarySearch(positions, offset); let _tailIndex = this.binarySearch(positions, offset + this.visibleHeight); // 找不到就为data.length if (_tailIndex === -1) { _tailIndex = data.length; } this.headIndex = Math.max(0, _headIndex); this.tailIndex = Math.min(this.ctx.maxRowIndex, _tailIndex + 1); this.visibleRows = data.slice(this.headIndex, this.tailIndex + 1); this.ctx.body.headIndex = this.headIndex; this.ctx.body.tailIndex = this.tailIndex; this.ctx.body.visibleRows = this.visibleRows; const rows = []; for (let i = 0; i < this.visibleRows.length; i++) { const index = this.headIndex + i; const data = this.visibleRows[i]; const { height, top } = this.ctx.database.getPositionForRowIndex(index); const row = new Row(this.ctx, index, 0, top + this.y, header.width, height, data); rows.push(row); } this.renderRows = rows; this.ctx.body.renderRows = rows; } draw() { this.renderRows.forEach((row) => { row.drawCenter(); }); this.drawFiexShadow(); this.renderRows.forEach((row) => { row.drawFixed(); }); this.drawTipLine(); } } //# sourceMappingURL=Body.js.map