mirror of
https://github.com/VisActor/VTable
synced 2024-11-21 09:30:54 +00:00
feat: optimize the performance of large data interaction (#3)
* optimize the performance of large data interaction * add pixel ratio and select api
This commit is contained in:
parent
9996b2b55c
commit
fc27cf1d61
2
.gitignore
vendored
2
.gitignore
vendored
@ -95,5 +95,5 @@ packages/vtable-docs/public/zh/documents
|
||||
packages/vtable-docs/public/en/documents
|
||||
packages/vtable-docs/public/*.html
|
||||
|
||||
#git-hook
|
||||
# git-hook
|
||||
common/scripts/pre-commit
|
@ -78,6 +78,7 @@ import {
|
||||
import { MenuHandler } from '../menu/dom/MenuHandler';
|
||||
import type { BaseTableAPI, BaseTableConstructorOptions, IBaseTableProtected } from '../ts-types/base-table';
|
||||
import { FocusInput } from './FouseInput';
|
||||
import { defaultPixelRatio } from '../tools/pixel-ratio';
|
||||
const { toBoxArray } = utilStyle;
|
||||
const { isTouchEvent } = event;
|
||||
const rangeReg = /^\$(\d+)\$(\d+)$/;
|
||||
@ -156,7 +157,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
|
||||
menu,
|
||||
select: click,
|
||||
customRender,
|
||||
pixelRatio = 1
|
||||
pixelRatio = defaultPixelRatio
|
||||
} = options;
|
||||
this.options = options;
|
||||
this._widthMode = widthMode;
|
||||
@ -700,7 +701,8 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
|
||||
* @param pixelRatio
|
||||
*/
|
||||
setPixelRatio(pixelRatio: number) {
|
||||
// do nothing
|
||||
this.internalProps.pixelRatio = pixelRatio;
|
||||
this.scenegraph.setPixelRatio(pixelRatio);
|
||||
}
|
||||
/**
|
||||
* 窗口尺寸发生变化 或者像数比变化
|
||||
@ -2030,7 +2032,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
|
||||
* 清除选中单元格
|
||||
*/
|
||||
clearSelected() {
|
||||
// do nothing
|
||||
this.stateManeger.updateSelectPos(-1, -1);
|
||||
}
|
||||
/**
|
||||
* 选中单元格 和鼠标选中单元格效果一致
|
||||
@ -2038,7 +2040,8 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI {
|
||||
* @param row
|
||||
*/
|
||||
selectCell(col: number, row: number) {
|
||||
// do nothing
|
||||
this.stateManeger.updateSelectPos(col, row);
|
||||
this.stateManeger.endSelectCells();
|
||||
}
|
||||
|
||||
abstract isListTable(): boolean;
|
||||
|
@ -3,6 +3,7 @@ import type { Group } from '../../graphic/group';
|
||||
import type { WrapText } from '../../graphic/text';
|
||||
import { updateCellHeightForColumn } from '../../layout/update-height';
|
||||
import type { Scenegraph } from '../../scenegraph';
|
||||
import { emptyGroup } from '../../scenegraph';
|
||||
import { getProp } from '../../utils/get-prop';
|
||||
import { getPadding } from '../../utils/padding';
|
||||
import { createColGroup } from '../column';
|
||||
@ -251,6 +252,8 @@ export class SceneProxy {
|
||||
// 不改变row,更新body group范围
|
||||
this.updateBody(y);
|
||||
}
|
||||
|
||||
this.scenegraph.updateNextFrame();
|
||||
}
|
||||
|
||||
updateBody(y: number) {
|
||||
@ -304,7 +307,7 @@ export class SceneProxy {
|
||||
for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) {
|
||||
for (let row = syncTopRow; row <= syncBottomRow; row++) {
|
||||
// const cellGroup = this.table.scenegraph.getCell(col, row);
|
||||
const cellGroup = this.highPerformanceGetCell(col, row);
|
||||
const cellGroup = this.highPerformanceGetCell(col, row, distStartRow, distEndRow);
|
||||
this.updateCellGroupContent(cellGroup);
|
||||
}
|
||||
}
|
||||
@ -367,7 +370,7 @@ export class SceneProxy {
|
||||
for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) {
|
||||
for (let row = syncTopRow; row <= syncBottomRow; row++) {
|
||||
// const cellGroup = this.table.scenegraph.getCell(col, row);
|
||||
const cellGroup = this.highPerformanceGetCell(col, row);
|
||||
const cellGroup = this.highPerformanceGetCell(col, row, distStartRow, distEndRow);
|
||||
this.updateCellGroupContent(cellGroup);
|
||||
}
|
||||
}
|
||||
@ -398,8 +401,6 @@ export class SceneProxy {
|
||||
(this.table as any).scenegraph.bodyGroup.firstChild.lastChild.row
|
||||
);
|
||||
|
||||
this.scenegraph.renderSceneGraph();
|
||||
|
||||
if (!this.table.internalProps.autoRowHeight) {
|
||||
await this.progress();
|
||||
}
|
||||
@ -524,7 +525,10 @@ export class SceneProxy {
|
||||
}
|
||||
}
|
||||
|
||||
highPerformanceGetCell(col: number, row: number) {
|
||||
highPerformanceGetCell(col: number, row: number, rowStart: number = this.rowStart, rowEnd: number = this.rowEnd) {
|
||||
if (row < rowStart || row > rowEnd) {
|
||||
return emptyGroup;
|
||||
}
|
||||
if (this.cellCache.get(col)) {
|
||||
const cacheCellGoup = this.cellCache.get(col);
|
||||
if ((cacheCellGoup._next || cacheCellGoup._prev) && Math.abs(cacheCellGoup.row - row) < row) {
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
createRowHeaderColGroup
|
||||
} from './group-creater/column';
|
||||
import type { WrapText } from './graphic/text';
|
||||
import { updateAutoColWidth } from './layout/auto-width';
|
||||
import { updateAutoRowHeight } from './layout/auto-height';
|
||||
import { getCellMergeInfo } from './utils/get-cell-merge';
|
||||
import { updateColWidth } from './layout/update-width';
|
||||
@ -22,21 +21,23 @@ import { createFrameBorder } from './style/frame-border';
|
||||
import { ResizeColumnHotSpotSize } from '../tools/global';
|
||||
import splitModule from './graphic/contributions';
|
||||
import { getProp } from './utils/get-prop';
|
||||
import { createCellContent, dealWithIcon } from './utils/text-icon-layout';
|
||||
import { dealWithIcon } from './utils/text-icon-layout';
|
||||
import { SceneProxy } from './group-creater/progress/proxy';
|
||||
import { SortOrder } from '../state/state';
|
||||
import type { ListTable } from '../ListTable';
|
||||
import type { TooltipOptions } from '../ts-types/tooltip';
|
||||
import { computeColWidth, computeColsWidth } from './layout/compute-col-width';
|
||||
import { getStyleTheme } from './group-creater/column-helper';
|
||||
import { moveHeaderPosition } from './layout/move-cell';
|
||||
import { updateCell } from './group-creater/cell-helper';
|
||||
import type { BaseTableAPI } from '../ts-types/base-table';
|
||||
import { updateAllSelectComponent, updateCellSelectBorder } from './select/update-select-border';
|
||||
import { createCellSelectBorder } from './select/create-select-border';
|
||||
import { moveSelectingRangeComponentsToSelectedRangeComponents } from './select/move-select-border';
|
||||
import { deleteAllSelectBorder, deleteLastSelectedRangeComponents } from './select/delete-select-border';
|
||||
|
||||
container.load(splitModule);
|
||||
|
||||
const groupForDebug = new Group({});
|
||||
groupForDebug.role = 'empty';
|
||||
export const emptyGroup = new Group({});
|
||||
emptyGroup.role = 'empty';
|
||||
/**
|
||||
* @description: 表格场景树,存储和管理表格全部的场景图元
|
||||
* @return {*}
|
||||
@ -74,7 +75,8 @@ export class Scenegraph {
|
||||
width: table.canvas.width,
|
||||
height: table.canvas.height,
|
||||
disableDirtyBounds: false,
|
||||
background: table.theme.underlayBackgroundColor
|
||||
background: table.theme.underlayBackgroundColor,
|
||||
dpr: table.internalProps.pixelRatio
|
||||
});
|
||||
|
||||
this.stage.defaultLayer.setTheme({
|
||||
@ -349,7 +351,14 @@ export class Scenegraph {
|
||||
cell = this.getCell(range.start.col, range.start.row);
|
||||
}
|
||||
|
||||
return cell || groupForDebug;
|
||||
return cell || emptyGroup;
|
||||
}
|
||||
|
||||
highPerformanceGetCell(col: number, row: number): Group {
|
||||
if (!this.isPivot && !this.transpose && !this.table.isHeader(col, row)) {
|
||||
return this.proxy.highPerformanceGetCell(col, row);
|
||||
}
|
||||
return this.getCell(col, row);
|
||||
}
|
||||
|
||||
getColGroup(col: number, isCornerOrColHeader = false): Group {
|
||||
@ -396,179 +405,7 @@ export class Scenegraph {
|
||||
this.stage.renderNextFrame();
|
||||
}
|
||||
resetAllSelectComponent() {
|
||||
this.selectingRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
const [startCol, startRow, endCol, endRow] = key.split('-');
|
||||
let cellsBounds;
|
||||
for (let i = parseInt(startCol, 10); i <= parseInt(endCol, 10); i++) {
|
||||
for (let j = parseInt(startRow, 10); j <= parseInt(endRow, 10); j++) {
|
||||
const cellGroup = this.getCell(i, j);
|
||||
cellGroup.AABBBounds.width(); // hack: globalAABBBounds可能不会自动更新,这里强制更新一下
|
||||
const bounds = cellGroup.globalAABBBounds;
|
||||
if (!cellsBounds) {
|
||||
cellsBounds = bounds;
|
||||
} else {
|
||||
cellsBounds.union(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
selectComp.rect.setAttributes({
|
||||
x: cellsBounds.x1 - this.tableGroup.attribute.x,
|
||||
y: cellsBounds.y1 - this.tableGroup.attribute.y,
|
||||
width: cellsBounds.width(),
|
||||
height: cellsBounds.height(),
|
||||
visible: true
|
||||
});
|
||||
|
||||
//#region 判断是不是按着表头部分的选中框 因为绘制层级的原因 线宽会被遮住一半,因此需要动态调整层级
|
||||
const isNearRowHeader =
|
||||
// this.table.scrollLeft === 0 &&
|
||||
parseInt(startCol, 10) === this.table.frozenColCount;
|
||||
const isNearColHeader =
|
||||
// this.table.scrollTop === 0 &&
|
||||
parseInt(startRow, 10) === this.table.frozenRowCount;
|
||||
if (
|
||||
(isNearRowHeader && selectComp.rect.attribute.stroke[3]) ||
|
||||
(isNearColHeader && selectComp.rect.attribute.stroke[0])
|
||||
) {
|
||||
if (isNearRowHeader) {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'columnHeader' ? this.cornerHeaderGroup : this.rowHeaderGroup
|
||||
);
|
||||
}
|
||||
if (isNearColHeader) {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'rowHeader' ? this.cornerHeaderGroup : this.colHeaderGroup
|
||||
);
|
||||
}
|
||||
//#region 调整层级后 滚动情况下会出现绘制范围出界 如body的选中框 渲染在了rowheader上面,所有需要调整选中框rect的 边界
|
||||
if (
|
||||
selectComp.rect.attribute.x < this.rowHeaderGroup.attribute.width &&
|
||||
this.table.scrollLeft > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'columnHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
x: selectComp.rect.attribute.x + (this.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x),
|
||||
width: selectComp.rect.attribute.width - (this.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x)
|
||||
});
|
||||
}
|
||||
if (
|
||||
selectComp.rect.attribute.y < this.colHeaderGroup.attribute.height &&
|
||||
this.table.scrollTop > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'rowHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
y: selectComp.rect.attribute.y + (this.colHeaderGroup.attribute.height - selectComp.rect.attribute.y),
|
||||
height:
|
||||
selectComp.rect.attribute.height - (this.colHeaderGroup.attribute.height - selectComp.rect.attribute.y)
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
} else {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'body'
|
||||
? this.bodyGroup
|
||||
: selectComp.role === 'columnHeader'
|
||||
? this.colHeaderGroup
|
||||
: selectComp.role === 'rowHeader'
|
||||
? this.rowHeaderGroup
|
||||
: this.cornerHeaderGroup
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
});
|
||||
this.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
const [startCol, startRow, endCol, endRow] = key.split('-');
|
||||
let cellsBounds;
|
||||
for (let i = parseInt(startCol, 10); i <= parseInt(endCol, 10); i++) {
|
||||
for (let j = parseInt(startRow, 10); j <= parseInt(endRow, 10); j++) {
|
||||
const cellGroup = this.getCell(i, j);
|
||||
cellGroup.AABBBounds.width(); // hack: globalAABBBounds可能不会自动更新,这里强制更新一下
|
||||
const bounds = cellGroup.globalAABBBounds;
|
||||
if (!cellsBounds) {
|
||||
cellsBounds = bounds;
|
||||
} else {
|
||||
cellsBounds.union(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
selectComp.rect.setAttributes({
|
||||
x: cellsBounds.x1 - this.tableGroup.attribute.x,
|
||||
y: cellsBounds.y1 - this.tableGroup.attribute.y,
|
||||
width: cellsBounds.width(),
|
||||
height: cellsBounds.height(),
|
||||
visible: true
|
||||
});
|
||||
|
||||
//#region 判断是不是按着表头部分的选中框 因为绘制层级的原因 线宽会被遮住一半,因此需要动态调整层级
|
||||
const isNearRowHeader =
|
||||
// this.table.scrollLeft === 0 &&
|
||||
parseInt(startCol, 10) === this.table.frozenColCount;
|
||||
const isNearColHeader =
|
||||
// this.table.scrollTop === 0 &&
|
||||
parseInt(startRow, 10) === this.table.frozenRowCount;
|
||||
if (
|
||||
(isNearRowHeader && selectComp.rect.attribute.stroke[3]) ||
|
||||
(isNearColHeader && selectComp.rect.attribute.stroke[0])
|
||||
) {
|
||||
if (isNearRowHeader) {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'columnHeader' ? this.cornerHeaderGroup : this.rowHeaderGroup
|
||||
);
|
||||
}
|
||||
if (isNearColHeader) {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'rowHeader' ? this.cornerHeaderGroup : this.colHeaderGroup
|
||||
);
|
||||
}
|
||||
//#region 调整层级后 滚动情况下会出现绘制范围出界 如body的选中框 渲染在了rowheader上面,所有需要调整选中框rect的 边界
|
||||
if (
|
||||
selectComp.rect.attribute.x < this.rowHeaderGroup.attribute.width &&
|
||||
this.table.scrollLeft > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'columnHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
x: selectComp.rect.attribute.x + (this.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x),
|
||||
width: selectComp.rect.attribute.width - (this.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x)
|
||||
});
|
||||
}
|
||||
if (
|
||||
selectComp.rect.attribute.y < this.colHeaderGroup.attribute.height &&
|
||||
this.table.scrollTop > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'rowHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
y: selectComp.rect.attribute.y + (this.colHeaderGroup.attribute.height - selectComp.rect.attribute.y),
|
||||
height:
|
||||
selectComp.rect.attribute.height - (this.colHeaderGroup.attribute.height - selectComp.rect.attribute.y)
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
} else {
|
||||
this.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'body'
|
||||
? this.bodyGroup
|
||||
: selectComp.role === 'columnHeader'
|
||||
? this.colHeaderGroup
|
||||
: selectComp.role === 'rowHeader'
|
||||
? this.rowHeaderGroup
|
||||
: this.cornerHeaderGroup
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
});
|
||||
}
|
||||
|
||||
removeInteractionBorder(col: number, row: number) {
|
||||
const cellGroup = this.getCell(col, row);
|
||||
cellGroup.setAttribute('highlightStroke', undefined);
|
||||
cellGroup.setAttribute('highlightStrokeArrayWidth', undefined);
|
||||
cellGroup.setAttribute('highlightStrokeArrayColor', undefined);
|
||||
updateAllSelectComponent(this);
|
||||
}
|
||||
|
||||
hideHoverIcon(col: number, row: number) {
|
||||
@ -626,6 +463,13 @@ export class Scenegraph {
|
||||
(cellGroup?.firstChild as any)?.activate?.(this.table);
|
||||
}
|
||||
|
||||
removeInteractionBorder(col: number, row: number) {
|
||||
const cellGroup = this.getCell(col, row);
|
||||
cellGroup.setAttribute('highlightStroke', undefined);
|
||||
cellGroup.setAttribute('highlightStrokeArrayWidth', undefined);
|
||||
cellGroup.setAttribute('highlightStrokeArrayColor', undefined);
|
||||
}
|
||||
|
||||
createCellSelectBorder(
|
||||
start_Col: number,
|
||||
start_Row: number,
|
||||
@ -635,238 +479,22 @@ export class Scenegraph {
|
||||
selectId: string, //整体区域${endRow}-${startCol}${startRow}${endCol}${endRow}作为其编号
|
||||
strokes?: boolean[]
|
||||
) {
|
||||
const startCol = Math.min(start_Col, end_Col);
|
||||
const startRow = Math.min(start_Row, end_Row);
|
||||
const endCol = Math.max(start_Col, end_Col);
|
||||
const endRow = Math.max(start_Row, end_Row);
|
||||
|
||||
let cellsBounds;
|
||||
for (let i = startCol; i <= endCol; i++) {
|
||||
for (let j = startRow; j <= endRow; j++) {
|
||||
const cellGroup = this.getCell(i, j);
|
||||
if (cellGroup.role === 'shadow-cell') {
|
||||
continue;
|
||||
}
|
||||
const bounds = cellGroup.globalAABBBounds;
|
||||
if (!cellsBounds) {
|
||||
cellsBounds = bounds;
|
||||
} else {
|
||||
cellsBounds.union(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const theme = this.table.theme;
|
||||
// 框选外边框
|
||||
const bodyClickBorderColor = theme.selectionStyle?.cellBorderColor;
|
||||
const bodyClickLineWidth = theme.selectionStyle?.cellBorderLineWidth;
|
||||
const rect = createRect({
|
||||
pickable: false,
|
||||
fill: true,
|
||||
fillColor: (theme.selectionStyle?.cellBgColor as any) ?? 'rgba(0, 0, 255,0.1)',
|
||||
strokeColor: bodyClickBorderColor as string,
|
||||
lineWidth: bodyClickLineWidth as number,
|
||||
stroke: strokes,
|
||||
x: cellsBounds.x1 - this.tableGroup.attribute.x,
|
||||
y: cellsBounds.y1 - this.tableGroup.attribute.y,
|
||||
width: cellsBounds.width(),
|
||||
height: cellsBounds.height(),
|
||||
visible: true
|
||||
});
|
||||
this.lastSelectId = selectId;
|
||||
this.selectingRangeComponents.set(`${startCol}-${startRow}-${endCol}-${endRow}-${selectId}`, {
|
||||
rect,
|
||||
role: selectRangeType
|
||||
});
|
||||
this.tableGroup.insertAfter(
|
||||
rect,
|
||||
selectRangeType === 'body'
|
||||
? this.bodyGroup
|
||||
: selectRangeType === 'columnHeader'
|
||||
? this.colHeaderGroup
|
||||
: selectRangeType === 'rowHeader'
|
||||
? this.rowHeaderGroup
|
||||
: this.cornerHeaderGroup
|
||||
);
|
||||
createCellSelectBorder(this, start_Col, start_Row, end_Col, end_Row, selectRangeType, selectId, strokes);
|
||||
}
|
||||
moveSelectingRangeComponentsToSelectedRangeComponents() {
|
||||
this.selectingRangeComponents.forEach((rangeComponent, key) => {
|
||||
if (this.selectedRangeComponents.get(key)) {
|
||||
this.selectedRangeComponents.get(key).rect.delete();
|
||||
}
|
||||
this.selectedRangeComponents.set(key, rangeComponent);
|
||||
});
|
||||
this.selectingRangeComponents = new Map();
|
||||
this.updateNextFrame();
|
||||
moveSelectingRangeComponentsToSelectedRangeComponents(this);
|
||||
}
|
||||
/** 按住shift 则继续上次选中范围 需要将现有的删除掉 */
|
||||
deleteLastSelectedRangeComponents() {
|
||||
this.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
const lastSelectId = key.split('-')[4];
|
||||
if (lastSelectId === this.lastSelectId) {
|
||||
selectComp.rect.delete();
|
||||
this.selectedRangeComponents.delete(key);
|
||||
}
|
||||
});
|
||||
deleteLastSelectedRangeComponents(this);
|
||||
}
|
||||
deleteAllSelectBorder() {
|
||||
this.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
selectComp.rect.delete();
|
||||
});
|
||||
this.selectedRangeComponents = new Map();
|
||||
deleteAllSelectBorder(this);
|
||||
}
|
||||
|
||||
updateCellSelectBorder(newStartCol: number, newStartRow: number, newEndCol: number, newEndRow: number) {
|
||||
let startCol = Math.min(newEndCol, newStartCol);
|
||||
let startRow = Math.min(newEndRow, newStartRow);
|
||||
let endCol = Math.max(newEndCol, newStartCol);
|
||||
let endRow = Math.max(newEndRow, newStartRow);
|
||||
//#region region 校验四周的单元格有没有合并的情况,如有则扩大范围
|
||||
const extendSelectRange = () => {
|
||||
let isExtend = false;
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
if (col === startCol) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const mergeInfo = getCellMergeInfo(this.table, col, row);
|
||||
if (mergeInfo && mergeInfo.start.col < startCol) {
|
||||
startCol = mergeInfo.start.col;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExtend && col === endCol) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const mergeInfo = getCellMergeInfo(this.table, col, row);
|
||||
if (mergeInfo && mergeInfo.end.col > endCol) {
|
||||
endCol = mergeInfo.end.col;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExtend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isExtend) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
if (row === startRow) {
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
const mergeInfo = getCellMergeInfo(this.table, col, row);
|
||||
if (mergeInfo && mergeInfo.start.row < startRow) {
|
||||
startRow = mergeInfo.start.row;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExtend && row === endRow) {
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
const mergeInfo = getCellMergeInfo(this.table, col, row);
|
||||
if (mergeInfo && mergeInfo.end.row > endRow) {
|
||||
endRow = mergeInfo.end.row;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExtend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isExtend) {
|
||||
extendSelectRange();
|
||||
}
|
||||
};
|
||||
extendSelectRange();
|
||||
//#endregion
|
||||
this.selectingRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
selectComp.rect.delete();
|
||||
});
|
||||
this.selectingRangeComponents = new Map();
|
||||
|
||||
let needRowHeader = false;
|
||||
let needColumnHeader = false;
|
||||
let needBody = false;
|
||||
let needCornerHeader = false;
|
||||
if (startCol <= this.table.frozenColCount - 1 && startRow <= this.table.frozenRowCount - 1) {
|
||||
needCornerHeader = true;
|
||||
}
|
||||
if (startCol <= this.table.frozenColCount - 1 && endRow >= this.table.frozenRowCount) {
|
||||
needRowHeader = true;
|
||||
}
|
||||
if (startRow <= this.table.frozenRowCount - 1 && endCol >= this.table.frozenColCount) {
|
||||
needColumnHeader = true;
|
||||
}
|
||||
if (endCol >= this.table.frozenColCount && endRow >= this.table.frozenRowCount) {
|
||||
needBody = true;
|
||||
}
|
||||
|
||||
// TODO 可以尝试不拆分三个表头和body【前提是theme中合并配置】 用一个SelectBorder 需要结合clip,并动态设置border的范围【依据区域范围 已经是否跨表头及body】
|
||||
if (needCornerHeader) {
|
||||
const cornerEndCol = Math.min(endCol, this.table.frozenColCount - 1);
|
||||
const cornerEndRow = Math.min(endRow, this.table.frozenRowCount - 1);
|
||||
const strokeArray = [true, !needColumnHeader, !needRowHeader, true];
|
||||
this.createCellSelectBorder(
|
||||
startCol,
|
||||
startRow,
|
||||
cornerEndCol,
|
||||
cornerEndRow,
|
||||
'cornerHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needColumnHeader) {
|
||||
const columnHeaderStartCol = Math.max(startCol, this.table.frozenColCount);
|
||||
const columnHeaderEndRow = Math.min(endRow, this.table.frozenRowCount - 1);
|
||||
const strokeArray = [true, true, !needBody, !needCornerHeader];
|
||||
this.createCellSelectBorder(
|
||||
columnHeaderStartCol,
|
||||
startRow,
|
||||
endCol,
|
||||
columnHeaderEndRow,
|
||||
'columnHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needRowHeader) {
|
||||
const columnHeaderStartRow = Math.max(startRow, this.table.frozenRowCount);
|
||||
const columnHeaderEndCol = Math.min(endCol, this.table.frozenColCount - 1);
|
||||
const strokeArray = [!needCornerHeader, !needBody, true, true];
|
||||
this.createCellSelectBorder(
|
||||
startCol,
|
||||
columnHeaderStartRow,
|
||||
columnHeaderEndCol,
|
||||
endRow,
|
||||
'rowHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needBody) {
|
||||
const columnHeaderStartCol = Math.max(startCol, this.table.frozenColCount);
|
||||
const columnHeaderStartRow = Math.max(startRow, this.table.frozenRowCount);
|
||||
const strokeArray = [!needColumnHeader, true, true, !needRowHeader];
|
||||
this.createCellSelectBorder(
|
||||
columnHeaderStartCol,
|
||||
columnHeaderStartRow,
|
||||
endCol,
|
||||
endRow,
|
||||
'body',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
updateCellSelectBorder(this, newStartCol, newStartRow, newEndCol, newEndRow);
|
||||
}
|
||||
// hideCellsSelectBorder() {
|
||||
// this.component.selectBorder.setAttribute('visible', false);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @description: 获取指定单元格指定位置的icon mark
|
||||
@ -1151,6 +779,8 @@ export class Scenegraph {
|
||||
|
||||
// 更新滚动条状态
|
||||
this.component.updateScrollBar();
|
||||
|
||||
this.updateNextFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1769,6 +1399,16 @@ export class Scenegraph {
|
||||
}
|
||||
updateCell(col, row, this.table);
|
||||
}
|
||||
|
||||
setPixelRatio(pixelRatio: number) {
|
||||
// this.stage.setDpr(pixelRatio);
|
||||
// 这里因为本时刻部分节点有更新bounds标记,直接render回导致开启DirtyBounds,无法完整重绘画布;
|
||||
// 所以这里先关闭DirtyBounds,等待下一帧再开启
|
||||
this.stage.disableDirtyBounds();
|
||||
this.stage.window.setDpr(pixelRatio);
|
||||
this.stage.render();
|
||||
this.stage.enableDirtyBounds();
|
||||
}
|
||||
}
|
||||
|
||||
function showIcon(scene: Scenegraph, cellGroup: Group, visibleTime: 'mouseenter_cell' | 'click_cell') {
|
||||
|
@ -0,0 +1,68 @@
|
||||
import { createRect } from '@visactor/vrender';
|
||||
import type { CellType } from '../../ts-types';
|
||||
import type { Scenegraph } from '../scenegraph';
|
||||
|
||||
export function createCellSelectBorder(
|
||||
scene: Scenegraph,
|
||||
start_Col: number,
|
||||
start_Row: number,
|
||||
end_Col: number,
|
||||
end_Row: number,
|
||||
selectRangeType: CellType,
|
||||
selectId: string, //整体区域${endRow}-${startCol}${startRow}${endCol}${endRow}作为其编号
|
||||
strokes?: boolean[]
|
||||
) {
|
||||
const startCol = Math.min(start_Col, end_Col);
|
||||
const startRow = Math.min(start_Row, end_Row);
|
||||
const endCol = Math.max(start_Col, end_Col);
|
||||
const endRow = Math.max(start_Row, end_Row);
|
||||
|
||||
let cellsBounds;
|
||||
for (let i = startCol; i <= endCol; i++) {
|
||||
for (let j = startRow; j <= endRow; j++) {
|
||||
const cellGroup = scene.highPerformanceGetCell(i, j);
|
||||
if (cellGroup.role === 'shadow-cell') {
|
||||
continue;
|
||||
}
|
||||
const bounds = cellGroup.globalAABBBounds;
|
||||
if (!cellsBounds) {
|
||||
cellsBounds = bounds;
|
||||
} else {
|
||||
cellsBounds.union(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const theme = scene.table.theme;
|
||||
// 框选外边框
|
||||
const bodyClickBorderColor = theme.selectionStyle?.cellBorderColor;
|
||||
const bodyClickLineWidth = theme.selectionStyle?.cellBorderLineWidth;
|
||||
const rect = createRect({
|
||||
pickable: false,
|
||||
fill: true,
|
||||
fillColor: (theme.selectionStyle?.cellBgColor as any) ?? 'rgba(0, 0, 255,0.1)',
|
||||
strokeColor: bodyClickBorderColor as string,
|
||||
lineWidth: bodyClickLineWidth as number,
|
||||
stroke: strokes,
|
||||
x: cellsBounds.x1 - scene.tableGroup.attribute.x,
|
||||
y: cellsBounds.y1 - scene.tableGroup.attribute.y,
|
||||
width: cellsBounds.width(),
|
||||
height: cellsBounds.height(),
|
||||
visible: true
|
||||
});
|
||||
scene.lastSelectId = selectId;
|
||||
scene.selectingRangeComponents.set(`${startCol}-${startRow}-${endCol}-${endRow}-${selectId}`, {
|
||||
rect,
|
||||
role: selectRangeType
|
||||
});
|
||||
scene.tableGroup.insertAfter(
|
||||
rect,
|
||||
selectRangeType === 'body'
|
||||
? scene.bodyGroup
|
||||
: selectRangeType === 'columnHeader'
|
||||
? scene.colHeaderGroup
|
||||
: selectRangeType === 'rowHeader'
|
||||
? scene.rowHeaderGroup
|
||||
: scene.cornerHeaderGroup
|
||||
);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import type { IRect } from '@visactor/vrender';
|
||||
import type { Scenegraph } from '../scenegraph';
|
||||
import type { CellType } from '../../ts-types';
|
||||
|
||||
/** 按住shift 则继续上次选中范围 需要将现有的删除掉 */
|
||||
export function deleteLastSelectedRangeComponents(scene: Scenegraph) {
|
||||
scene.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
const lastSelectId = key.split('-')[4];
|
||||
if (lastSelectId === scene.lastSelectId) {
|
||||
selectComp.rect.delete();
|
||||
scene.selectedRangeComponents.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteAllSelectBorder(scene: Scenegraph) {
|
||||
scene.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
selectComp.rect.delete();
|
||||
});
|
||||
scene.selectedRangeComponents = new Map();
|
||||
}
|
12
packages/vtable/src/scenegraph/select/move-select-border.ts
Normal file
12
packages/vtable/src/scenegraph/select/move-select-border.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Scenegraph } from '../scenegraph';
|
||||
|
||||
export function moveSelectingRangeComponentsToSelectedRangeComponents(scene: Scenegraph) {
|
||||
scene.selectingRangeComponents.forEach((rangeComponent, key) => {
|
||||
if (scene.selectedRangeComponents.get(key)) {
|
||||
scene.selectedRangeComponents.get(key).rect.delete();
|
||||
}
|
||||
scene.selectedRangeComponents.set(key, rangeComponent);
|
||||
});
|
||||
scene.selectingRangeComponents = new Map();
|
||||
scene.updateNextFrame();
|
||||
}
|
265
packages/vtable/src/scenegraph/select/update-select-border.ts
Normal file
265
packages/vtable/src/scenegraph/select/update-select-border.ts
Normal file
@ -0,0 +1,265 @@
|
||||
import type { IRect } from '@visactor/vrender';
|
||||
import type { Scenegraph } from '../scenegraph';
|
||||
import type { CellType } from '../../ts-types';
|
||||
import { getCellMergeInfo } from '../utils/get-cell-merge';
|
||||
|
||||
export function updateAllSelectComponent(scene: Scenegraph) {
|
||||
scene.selectingRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
updateComponent(selectComp, key, scene);
|
||||
});
|
||||
scene.selectedRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
updateComponent(selectComp, key, scene);
|
||||
});
|
||||
}
|
||||
|
||||
function updateComponent(selectComp: { rect: IRect; role: CellType }, key: string, scene: Scenegraph) {
|
||||
const [startColStr, startRowStr, endColStr, endRowStr] = key.split('-');
|
||||
const startCol = parseInt(startColStr, 10);
|
||||
const startRow = parseInt(startRowStr, 10);
|
||||
const endCol = parseInt(endColStr, 10);
|
||||
const endRow = parseInt(endRowStr, 10);
|
||||
let cellsBounds;
|
||||
for (let i = startCol; i <= endCol; i++) {
|
||||
for (let j = startRow; j <= endRow; j++) {
|
||||
const cellGroup = scene.highPerformanceGetCell(i, j);
|
||||
if (cellGroup.role !== 'cell') {
|
||||
continue;
|
||||
}
|
||||
cellGroup.AABBBounds.width(); // hack: globalAABBBounds可能不会自动更新,这里强制更新一下
|
||||
const bounds = cellGroup.globalAABBBounds;
|
||||
if (!cellsBounds) {
|
||||
cellsBounds = bounds;
|
||||
} else {
|
||||
cellsBounds.union(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cellsBounds) {
|
||||
// 选中区域在实际单元格区域外,不显示选择框
|
||||
selectComp.rect.setAttributes({
|
||||
visible: false
|
||||
});
|
||||
} else {
|
||||
selectComp.rect.setAttributes({
|
||||
x: cellsBounds.x1 - scene.tableGroup.attribute.x,
|
||||
y: cellsBounds.y1 - scene.tableGroup.attribute.y,
|
||||
width: cellsBounds.width(),
|
||||
height: cellsBounds.height(),
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
//#region 判断是不是按着表头部分的选中框 因为绘制层级的原因 线宽会被遮住一半,因此需要动态调整层级
|
||||
const isNearRowHeader =
|
||||
// scene.table.scrollLeft === 0 &&
|
||||
startCol === scene.table.frozenColCount;
|
||||
const isNearColHeader =
|
||||
// scene.table.scrollTop === 0 &&
|
||||
startRow === scene.table.frozenRowCount;
|
||||
if (
|
||||
(isNearRowHeader && selectComp.rect.attribute.stroke[3]) ||
|
||||
(isNearColHeader && selectComp.rect.attribute.stroke[0])
|
||||
) {
|
||||
if (isNearRowHeader) {
|
||||
scene.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'columnHeader' ? scene.cornerHeaderGroup : scene.rowHeaderGroup
|
||||
);
|
||||
}
|
||||
if (isNearColHeader) {
|
||||
scene.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'rowHeader' ? scene.cornerHeaderGroup : scene.colHeaderGroup
|
||||
);
|
||||
}
|
||||
//#region 调整层级后 滚动情况下会出现绘制范围出界 如body的选中框 渲染在了rowheader上面,所有需要调整选中框rect的 边界
|
||||
if (
|
||||
selectComp.rect.attribute.x < scene.rowHeaderGroup.attribute.width &&
|
||||
scene.table.scrollLeft > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'columnHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
x: selectComp.rect.attribute.x + (scene.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x),
|
||||
width: selectComp.rect.attribute.width - (scene.rowHeaderGroup.attribute.width - selectComp.rect.attribute.x)
|
||||
});
|
||||
}
|
||||
if (
|
||||
selectComp.rect.attribute.y < scene.colHeaderGroup.attribute.height &&
|
||||
scene.table.scrollTop > 0 &&
|
||||
(selectComp.role === 'body' || selectComp.role === 'rowHeader')
|
||||
) {
|
||||
selectComp.rect.setAttributes({
|
||||
y: selectComp.rect.attribute.y + (scene.colHeaderGroup.attribute.height - selectComp.rect.attribute.y),
|
||||
height: selectComp.rect.attribute.height - (scene.colHeaderGroup.attribute.height - selectComp.rect.attribute.y)
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
} else {
|
||||
scene.tableGroup.insertAfter(
|
||||
selectComp.rect,
|
||||
selectComp.role === 'body'
|
||||
? scene.bodyGroup
|
||||
: selectComp.role === 'columnHeader'
|
||||
? scene.colHeaderGroup
|
||||
: selectComp.role === 'rowHeader'
|
||||
? scene.rowHeaderGroup
|
||||
: scene.cornerHeaderGroup
|
||||
);
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export function updateCellSelectBorder(
|
||||
scene: Scenegraph,
|
||||
newStartCol: number,
|
||||
newStartRow: number,
|
||||
newEndCol: number,
|
||||
newEndRow: number
|
||||
) {
|
||||
let startCol = Math.min(newEndCol, newStartCol);
|
||||
let startRow = Math.min(newEndRow, newStartRow);
|
||||
let endCol = Math.max(newEndCol, newStartCol);
|
||||
let endRow = Math.max(newEndRow, newStartRow);
|
||||
//#region region 校验四周的单元格有没有合并的情况,如有则扩大范围
|
||||
const extendSelectRange = () => {
|
||||
let isExtend = false;
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
if (col === startCol) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const mergeInfo = getCellMergeInfo(scene.table, col, row);
|
||||
if (mergeInfo && mergeInfo.start.col < startCol) {
|
||||
startCol = mergeInfo.start.col;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExtend && col === endCol) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
const mergeInfo = getCellMergeInfo(scene.table, col, row);
|
||||
if (mergeInfo && mergeInfo.end.col > endCol) {
|
||||
endCol = mergeInfo.end.col;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExtend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isExtend) {
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
if (row === startRow) {
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
const mergeInfo = getCellMergeInfo(scene.table, col, row);
|
||||
if (mergeInfo && mergeInfo.start.row < startRow) {
|
||||
startRow = mergeInfo.start.row;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isExtend && row === endRow) {
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
const mergeInfo = getCellMergeInfo(scene.table, col, row);
|
||||
if (mergeInfo && mergeInfo.end.row > endRow) {
|
||||
endRow = mergeInfo.end.row;
|
||||
isExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExtend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isExtend) {
|
||||
extendSelectRange();
|
||||
}
|
||||
};
|
||||
extendSelectRange();
|
||||
//#endregion
|
||||
scene.selectingRangeComponents.forEach((selectComp: { rect: IRect; role: CellType }, key: string) => {
|
||||
selectComp.rect.delete();
|
||||
});
|
||||
scene.selectingRangeComponents = new Map();
|
||||
|
||||
let needRowHeader = false;
|
||||
let needColumnHeader = false;
|
||||
let needBody = false;
|
||||
let needCornerHeader = false;
|
||||
if (startCol <= scene.table.frozenColCount - 1 && startRow <= scene.table.frozenRowCount - 1) {
|
||||
needCornerHeader = true;
|
||||
}
|
||||
if (startCol <= scene.table.frozenColCount - 1 && endRow >= scene.table.frozenRowCount) {
|
||||
needRowHeader = true;
|
||||
}
|
||||
if (startRow <= scene.table.frozenRowCount - 1 && endCol >= scene.table.frozenColCount) {
|
||||
needColumnHeader = true;
|
||||
}
|
||||
if (endCol >= scene.table.frozenColCount && endRow >= scene.table.frozenRowCount) {
|
||||
needBody = true;
|
||||
}
|
||||
|
||||
// TODO 可以尝试不拆分三个表头和body【前提是theme中合并配置】 用一个SelectBorder 需要结合clip,并动态设置border的范围【依据区域范围 已经是否跨表头及body】
|
||||
if (needCornerHeader) {
|
||||
const cornerEndCol = Math.min(endCol, scene.table.frozenColCount - 1);
|
||||
const cornerEndRow = Math.min(endRow, scene.table.frozenRowCount - 1);
|
||||
const strokeArray = [true, !needColumnHeader, !needRowHeader, true];
|
||||
scene.createCellSelectBorder(
|
||||
startCol,
|
||||
startRow,
|
||||
cornerEndCol,
|
||||
cornerEndRow,
|
||||
'cornerHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needColumnHeader) {
|
||||
const columnHeaderStartCol = Math.max(startCol, scene.table.frozenColCount);
|
||||
const columnHeaderEndRow = Math.min(endRow, scene.table.frozenRowCount - 1);
|
||||
const strokeArray = [true, true, !needBody, !needCornerHeader];
|
||||
scene.createCellSelectBorder(
|
||||
columnHeaderStartCol,
|
||||
startRow,
|
||||
endCol,
|
||||
columnHeaderEndRow,
|
||||
'columnHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needRowHeader) {
|
||||
const columnHeaderStartRow = Math.max(startRow, scene.table.frozenRowCount);
|
||||
const columnHeaderEndCol = Math.min(endCol, scene.table.frozenColCount - 1);
|
||||
const strokeArray = [!needCornerHeader, !needBody, true, true];
|
||||
scene.createCellSelectBorder(
|
||||
startCol,
|
||||
columnHeaderStartRow,
|
||||
columnHeaderEndCol,
|
||||
endRow,
|
||||
'rowHeader',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
if (needBody) {
|
||||
const columnHeaderStartCol = Math.max(startCol, scene.table.frozenColCount);
|
||||
const columnHeaderStartRow = Math.max(startRow, scene.table.frozenRowCount);
|
||||
const strokeArray = [!needColumnHeader, true, true, !needRowHeader];
|
||||
scene.createCellSelectBorder(
|
||||
columnHeaderStartCol,
|
||||
columnHeaderStartRow,
|
||||
endCol,
|
||||
endRow,
|
||||
'body',
|
||||
`${startCol}${startRow}${endCol}${endRow}`,
|
||||
strokeArray
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import type { CellRange } from '../../ts-types';
|
||||
import type { CellRange, TextColumnDefine } from '../../ts-types';
|
||||
import type { BaseTableAPI } from '../../ts-types/base-table';
|
||||
|
||||
/**
|
||||
@ -9,6 +9,10 @@ import type { BaseTableAPI } from '../../ts-types/base-table';
|
||||
* @return {false | CellRange}
|
||||
*/
|
||||
export function getCellMergeInfo(table: BaseTableAPI, col: number, row: number): false | CellRange {
|
||||
// 先判断非表头且非cellMerge配置,返回false
|
||||
if (!table.isHeader(col, row) && (table.getBodyColumnDefine(col, row) as TextColumnDefine).mergeCell !== true) {
|
||||
return false;
|
||||
}
|
||||
const range = table.getCellRange(col, row);
|
||||
const isMerge = range.start.col !== range.end.col || range.start.row !== range.end.row;
|
||||
if (!isMerge) {
|
||||
|
18
packages/vtable/src/tools/pixel-ratio.ts
Normal file
18
packages/vtable/src/tools/pixel-ratio.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { isNode } from './helper';
|
||||
|
||||
export let defaultPixelRatio = 1;
|
||||
/*
|
||||
* @Description: 设置像素比
|
||||
*/
|
||||
function setPixelRatio(): void {
|
||||
if (isNode) {
|
||||
defaultPixelRatio = 1;
|
||||
} else {
|
||||
defaultPixelRatio = Math.ceil(window.devicePixelRatio || 1);
|
||||
if (defaultPixelRatio > 1 && defaultPixelRatio % 2 !== 0) {
|
||||
// 非整数倍的像素比,向上取整
|
||||
defaultPixelRatio += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
setPixelRatio();
|
Loading…
Reference in New Issue
Block a user