perf(grid): optimize bulk edit performance

This commit is contained in:
GIWTO authored and GitHub committed 2026-07-25 20:06:25 +08:00
1 parent e6da0a6298
commit 1e21a5ad52
3 files changed
+151 -39

No files matched your search

@@ -1,4 +1,4 @@
import { ref, computed, nextTick, watch, getCurrentInstance, onActivated, onBeforeUnmount, onDeactivated, onMounted, toRaw, type ComputedRef, type Ref } from "vue";
import { ref, shallowRef, computed, nextTick, watch, getCurrentInstance, onActivated, onBeforeUnmount, onDeactivated, onMounted, toRaw, type ComputedRef, type Ref } from "vue";
import * as api from "@/lib/backend/api";
import type { CellValue } from "@/lib/dataGrid/cellValue";
import { coerceDataGridCellValue, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion";
@@ -206,7 +206,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const editingCell = ref<{ rowId: number; col: number } | null>(null);
const editValue = ref("");
const scrollerRef = ref<GridScrollerRef | null>(null);
const dirtyRows = ref<Map<number, Map<number, CellValue>>>(new Map());
const dirtyRows = shallowRef<Map<number, Map<number, CellValue>>>(new Map());
const newRows = ref<CellValue[][]>([]);
const deletedRows = ref<Set<number>>(new Set());
const quickEntryDraftRow = ref<CellValue[]>([]);
@@ -500,10 +500,45 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}) as CellValue;
}
let isBatching = false;
let batchUndoSnapshotPushed = false;
let batchMutated = false;
let batchColumnInfoCache: Map<number, ColumnInfo | undefined> | null = null;
function beginBatch() {
if (isBatching) return;
isBatching = true;
batchUndoSnapshotPushed = false;
batchMutated = false;
batchColumnInfoCache = new Map();
}
function commitBatch() {
if (!isBatching) return;
isBatching = false;
batchColumnInfoCache = null;
if (!batchMutated) return;
dirtyRows.value = new Map(dirtyRows.value);
newRows.value = [...newRows.value];
touchPendingChanges();
}
function markBatchMutated() {
if (isBatching) batchMutated = true;
}
function tableColumnForGridColumn(columnIndex: number): ColumnInfo | undefined {
if (isBatching && batchColumnInfoCache) {
if (batchColumnInfoCache.has(columnIndex)) {
return batchColumnInfoCache.get(columnIndex);
}
}
const columnName = sourceColumns.value?.[columnIndex] ?? result.value.columns[columnIndex];
if (!columnName) return undefined;
return tableMeta.value?.columns.find((column) => column.name.toLowerCase() === columnName.toLowerCase());
const info = columnName ? tableMeta.value?.columns.find((column) => column.name.toLowerCase() === columnName.toLowerCase()) : undefined;
if (isBatching && batchColumnInfoCache) {
batchColumnInfoCache.set(columnIndex, info);
}
return info;
}
function canEditColumn(columnIndex: number): boolean {
@@ -747,9 +782,19 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const nextDraftRow = [...quickEntryDraftRow.value];
nextDraftRow[col] = value === null ? null : coerceCellValue(value, oldVal, col, options);
if (nextDraftRow[col] === oldVal) return;
pushUndoSnapshot();
if (isBatching) {
if (!batchUndoSnapshotPushed) {
pushUndoSnapshot();
batchUndoSnapshotPushed = true;
}
} else {
pushUndoSnapshot();
}
quickEntryDraftRow.value = draftRowHasValue(nextDraftRow) ? nextDraftRow : emptyDraftRow();
touchPendingChanges();
markBatchMutated();
if (!isBatching) {
touchPendingChanges();
}
scheduleQuickEntryDraftPromotion();
return;
}
@@ -761,10 +806,20 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const oldVal = row[col];
const newVal = value === null ? null : coerceCellValue(value, oldVal, col, options);
if (newVal === oldVal) return;
pushUndoSnapshot();
if (isBatching) {
if (!batchUndoSnapshotPushed) {
pushUndoSnapshot();
batchUndoSnapshotPushed = true;
}
} else {
pushUndoSnapshot();
}
row[col] = newVal;
newRows.value = [...newRows.value];
touchPendingChanges();
markBatchMutated();
if (!isBatching) {
newRows.value = [...newRows.value];
touchPendingChanges();
}
return;
}
@@ -778,19 +833,39 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const newVal = value === null ? null : coerceCellValue(value, oldVal, col, options);
if (newVal === currentVal) return;
if (newVal !== oldVal) {
pushUndoSnapshot();
if (isBatching) {
if (!batchUndoSnapshotPushed) {
pushUndoSnapshot();
batchUndoSnapshotPushed = true;
}
} else {
pushUndoSnapshot();
}
if (!dirtyRows.value.has(item.sourceIndex)) dirtyRows.value.set(item.sourceIndex, new Map());
dirtyRows.value.get(item.sourceIndex)!.set(col, newVal);
markBatchMutated();
if (useTransaction.value && !transactionActive.value) {
enterTransaction();
}
} else {
if (hasPendingCellChange) pushUndoSnapshot();
if (hasPendingCellChange) {
if (isBatching) {
if (!batchUndoSnapshotPushed) {
pushUndoSnapshot();
batchUndoSnapshotPushed = true;
}
} else {
pushUndoSnapshot();
}
}
rowChanges?.delete(col);
if (rowChanges?.size === 0) dirtyRows.value.delete(item.sourceIndex);
if (hasPendingCellChange) markBatchMutated();
}
if (!isBatching) {
dirtyRows.value = new Map(dirtyRows.value);
touchPendingChanges();
}
dirtyRows.value = new Map(dirtyRows.value);
touchPendingChanges();
}
function restoreCellValue(rowId: number, col: number) {
@@ -1330,9 +1405,9 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
function discardChanges() {
dirtyRows.value.clear();
dirtyRows.value = new Map();
newRows.value = [];
deletedRows.value.clear();
deletedRows.value = new Set();
quickEntryDraftRow.value = emptyDraftRow();
queuedAutoSaveChanges.clear();
editingCell.value = null;
@@ -1477,6 +1552,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
isSaving,
saveError,
useTransaction,
beginBatch,
commitBatch,
enterTransaction,
exitTransaction,
startEdit,