fix(grid): preserve current page on data refresh instead of resetting to page 1

* @
fix(grid): preserve current page on data refresh instead of resetting to page 1

When refreshing table data (toolbar button, Ctrl+R, or auto-refresh), the
offset was hardcoded to 0, causing pagination to always jump back to the
first page. This was frustrating for users viewing later pages.

Use the current page offset (currentPage - 1) * pageSize so the user
stays on the same page after refresh. In infinite-scroll mode, the
offset still evaluates to 0 because resetInfiniteScrollState() sets
currentPage = 1, so behavior is unchanged.
@

* @
fix(grid): auto-redirect to last page when refreshed page no longer exists

Extends the refresh pagination fix with a safety net: when data is deleted
while viewing a later page (e.g. page 5 with only 2 pages remaining), the
grid auto-navigates to the last available page instead of showing an empty
page.

Uses a transient isRefreshingData flag to distinguish refresh/rollback
from normal pagination navigation, avoiding false triggers.
@

* Revert "@"

This reverts commit ff0bf0bb31.

* @
fix(grid): auto-redirect to last page when refreshed page no longer exists

When data is deleted while viewing a later page, the grid now auto-navigates
to the last available page instead of showing an empty page.

Uses a transient isRefreshingData flag to distinguish refresh/rollback
from normal pagination, and a loading-transition watcher (true->false)
placed after displayedTotalRowCount declaration to avoid TDZ errors.
@

* @
test(grid): add pagination offset-preservation and auto-redirect tests

Cover the two new behaviors added to DataGrid.vue:

1. dataGridPagination.test.ts — auto-redirect page calculation:
   - page beyond last page after data deletion → triggers redirect
   - page still valid → no redirect
   - fewer rows than one page → redirects to page 1
   - total=0 / total=undefined → guard prevents redirect

2. useDataGridActions.test.ts — offset preserved on reload:
   - onReloadData(offset=400) passes offset=400 to build-table-select-sql
   - resultPageOffset stored as 400 (not reset to 0)
@
This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-07-08 13:28:36 +08:00
1 parent 2929b1c035
commit 0901f176b0
3 files changed
+126 -2

No files matched your search

+28 -2
View File
@@ -3364,6 +3364,10 @@ const isInfiniteScrollPaginating = ref(false);
let lastInfiniteScrollPage = 0;
let infiniteScrollCheckScheduled = false;
let infiniteScrollAllLoaded = false;
// Tracks whether the current loading cycle was triggered by a refresh/rollback
// (as opposed to a normal paginate). Used to decide whether to auto-redirect
// when the current page no longer exists after data was deleted.
const isRefreshingData = ref(false);
watch(pageSize, (value) => {
customPageSizeInput.value = String(value);
});
@@ -3454,6 +3458,26 @@ const canGoNextPage = computed(() => {
const canJumpLastPage = computed(() => canGoNextPage.value && (hasKnownTotalRowCount.value || allRowsLoaded.value || !!props.tableMeta || !!props.countSql));
const totalRowCountBusy = computed(() => props.totalRowCountLoading === true || manualTotalRowCountLoading.value);
const canCalculateTotalRowCount = computed(() => !isResultsContext.value && !!props.connectionId && (!!props.tableMeta || !!props.countSql));
// When a refresh/rollback completes and the current page exceeds the last
// available page (e.g. data was deleted while viewing), auto-navigate to the
// last available page instead of showing an empty page.
watch(
() => props.loading,
(loading, prevLoading) => {
// Only act when loading completes (transitions from true to false)
// and the completion was triggered by a refresh/rollback.
if (!loading && prevLoading && isRefreshingData.value) {
isRefreshingData.value = false;
const total = displayedTotalRowCount.value;
if (!total || total <= 0) return;
const lastPageNum = Math.max(1, Math.ceil(total / pageSize.value));
if (currentPage.value <= lastPageNum) return;
currentPage.value = lastPageNum;
resetGridVerticalScroll(true);
emit("paginate", (lastPageNum - 1) * pageSize.value, pageSize.value, currentWhereInput(), currentOrderBy());
}
},
);
const showQueryEditReadyBadge = computed(() => isResultsContext.value && hasData.value && !!props.editable && (!!props.tableMeta || !!props.customSaveHandler));
const queryEditReadyTargetLabel = computed(() => props.tableMeta?.tableName ?? props.customSaveHandler?.targetLabel ?? "");
const showKeylessEditWarning = computed(() => !!props.editable && !!props.tableMeta && canUseKeylessRowPredicate(props.databaseType, props.tableMeta.primaryKeys ?? []));
@@ -4034,7 +4058,8 @@ async function onToolbarRefresh() {
resetInfiniteScrollState();
}
preserveTransposeOnNextResult.value = showTranspose.value;
emit("reload", props.sql, searchText.value, currentWhereInput(), currentOrderBy(), pageSize.value, 0);
isRefreshingData.value = true;
emit("reload", props.sql, searchText.value, currentWhereInput(), currentOrderBy(), pageSize.value, (currentPage.value - 1) * pageSize.value);
}
function stopAutoRefreshTimer() {
@@ -4078,7 +4103,8 @@ function onToolbarRollback() {
if (infiniteScrollEnabled.value) {
resetInfiniteScrollState();
}
emit("reload", props.sql, searchText.value, currentWhereInput(), currentOrderBy(), pageSize.value, 0);
isRefreshingData.value = true;
emit("reload", props.sql, searchText.value, currentWhereInput(), currentOrderBy(), pageSize.value, (currentPage.value - 1) * pageSize.value);
}
function addRow() {