feat: shared ErrorBanner component, CustomSaveHandler with preview, and cleanup
This commit is contained in:
parent
9fbafa9a6d
commit
514a01b3b1
|
|
@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import ErrorBanner from "@/components/ui/ErrorBanner.vue";
|
||||
import DataGrid from "@/components/grid/DataGrid.vue";
|
||||
import * as api from "@/lib/api";
|
||||
import { clampSearchSplitWidth } from "@/lib/dataGridSearchSplit";
|
||||
|
|
@ -18,6 +19,7 @@ import { useSettingsStore } from "@/stores/settingsStore";
|
|||
import JsonEditNode from "./JsonEditNode.vue";
|
||||
import type { EditNode } from "@/types/editor";
|
||||
import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
import type { CustomSaveHandler } from "@/composables/useDataGridEditor";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
|
||||
|
|
@ -256,6 +258,110 @@ async function gridSave(changes: { dirtyRows: Map<number, Map<number, string | n
|
|||
await load();
|
||||
}
|
||||
|
||||
function formatMongoValue(val: unknown): string {
|
||||
if (val === null || val === undefined) return "null";
|
||||
if (typeof val === "number" || typeof val === "boolean") return String(val);
|
||||
if (typeof val === "string") return JSON.stringify(val);
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
function mongoIdPreview(val: unknown): string {
|
||||
if (val === null || val === undefined) return "null";
|
||||
if (typeof val === "string" && /^[a-fA-F0-9]{24}$/.test(val)) return `ObjectId("${val}")`;
|
||||
return formatMongoValue(val);
|
||||
}
|
||||
|
||||
function buildUpdateDoc(changes: Map<number, string | number | boolean | null>, columns: string[]): Record<string, unknown> {
|
||||
const setFields: Record<string, unknown> = {};
|
||||
const unsetFields: Record<string, unknown> = {};
|
||||
for (const [colIdx, newVal] of changes) {
|
||||
const col = columns[colIdx];
|
||||
if (!col || col === "_id") continue;
|
||||
if (newVal === null) {
|
||||
unsetFields[col] = "";
|
||||
} else if (typeof newVal === "string") {
|
||||
try {
|
||||
setFields[col] = JSON.parse(newVal);
|
||||
} catch {
|
||||
setFields[col] = newVal;
|
||||
}
|
||||
} else {
|
||||
setFields[col] = newVal;
|
||||
}
|
||||
}
|
||||
const doc: Record<string, unknown> = {};
|
||||
if (Object.keys(setFields).length > 0) doc.$set = setFields;
|
||||
if (Object.keys(unsetFields).length > 0) doc.$unset = unsetFields;
|
||||
return doc;
|
||||
}
|
||||
|
||||
function buildNewDoc(newRow: (string | number | boolean | null)[], columns: string[]): Record<string, unknown> {
|
||||
const doc: Record<string, unknown> = {};
|
||||
for (let ci = 0; ci < columns.length; ci++) {
|
||||
const col = columns[ci];
|
||||
if (!col || col === "_id") continue;
|
||||
const val = newRow[ci];
|
||||
if (val === null) continue;
|
||||
if (typeof val === "string") {
|
||||
try {
|
||||
doc[col] = JSON.parse(val);
|
||||
} catch {
|
||||
doc[col] = val;
|
||||
}
|
||||
} else {
|
||||
doc[col] = val;
|
||||
}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
async function previewDocumentChanges(changes: { dirtyRows: Map<number, Map<number, string | number | boolean | null>>; deletedRows: Set<number>; newRows: (string | number | boolean | null)[][]; columns: string[]; rows: (string | number | boolean | null)[][] }): Promise<string[]> {
|
||||
const { dirtyRows, deletedRows, newRows, columns, rows } = changes;
|
||||
const idColIdx = columns.indexOf("_id");
|
||||
const stmts: string[] = [];
|
||||
const coll = props.collection;
|
||||
const isEs = documentStoreProvider.value.kind === "elasticsearch";
|
||||
|
||||
for (const [rowIdx, dirtyCols] of dirtyRows) {
|
||||
const row = rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id == null) continue;
|
||||
const updateDoc = buildUpdateDoc(dirtyCols, columns);
|
||||
if (isEs) {
|
||||
stmts.push(`POST /${coll}/_update/${JSON.stringify(String(id))}\n${JSON.stringify({ doc: updateDoc.$set ?? updateDoc }, null, 2)}`);
|
||||
} else {
|
||||
stmts.push(`db.${coll}.updateOne({_id: ${mongoIdPreview(id)}}, ${JSON.stringify(updateDoc)})`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rowIdx of deletedRows) {
|
||||
const row = rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id == null) continue;
|
||||
if (isEs) {
|
||||
stmts.push(`DELETE /${coll}/_doc/${JSON.stringify(String(id))}`);
|
||||
} else {
|
||||
stmts.push(`db.${coll}.deleteOne({_id: ${mongoIdPreview(id)}})`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const newRow of newRows) {
|
||||
const doc = buildNewDoc(newRow, columns);
|
||||
if (isEs) {
|
||||
stmts.push(`POST /${coll}/_doc\n${JSON.stringify(doc, null, 2)}`);
|
||||
} else {
|
||||
stmts.push(`db.${coll}.insertOne(${JSON.stringify(doc)})`);
|
||||
}
|
||||
}
|
||||
|
||||
return stmts;
|
||||
}
|
||||
|
||||
const customSaveHandler = computed<CustomSaveHandler>(() => ({
|
||||
save: gridSave,
|
||||
preview: previewDocumentChanges,
|
||||
}));
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
|
|
@ -688,7 +794,7 @@ function resetTableSearchSplitWidth() {
|
|||
:result="gridResult"
|
||||
context="results"
|
||||
editable
|
||||
:custom-save="gridSave"
|
||||
:custom-save-handler="customSaveHandler"
|
||||
:loading="loading"
|
||||
:sql="documentStoreLabels.queryPreview"
|
||||
:page-offset="page * pageSize"
|
||||
|
|
@ -932,9 +1038,7 @@ function resetTableSearchSplitWidth() {
|
|||
{{ t("mongo.selectDocument") }}
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="px-3 py-1.5 border-t bg-destructive/10 text-destructive text-xs shrink-0">
|
||||
{{ error }}
|
||||
</div>
|
||||
<ErrorBanner v-if="error" :message="error" />
|
||||
<DangerConfirmDialog v-model:open="showDeleteConfirm" :message="t('dangerDialog.deleteMessage')" :details="deleteDetails" :confirm-label="t('dangerDialog.deleteConfirm')" @confirm="confirmDelete" />
|
||||
</div>
|
||||
</Pane>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import {
|
|||
WrapText,
|
||||
Info,
|
||||
Rows3,
|
||||
TriangleAlert,
|
||||
RefreshCcw,
|
||||
RotateCcw,
|
||||
Pencil,
|
||||
|
|
@ -72,6 +71,7 @@ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@/compon
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import ErrorBanner from "@/components/ui/ErrorBanner.vue";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import ImagePreviewDialog from "@/components/grid/ImagePreviewDialog.vue";
|
||||
|
|
@ -181,7 +181,7 @@ const props = defineProps<{
|
|||
cacheKey?: string;
|
||||
onExecuteSql?: (sql: string) => Promise<void>;
|
||||
fullExportResult?: () => Promise<QueryResult | undefined>;
|
||||
customSave?: (changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<void>;
|
||||
customSaveHandler?: import("@/composables/useDataGridEditor").CustomSaveHandler;
|
||||
}>();
|
||||
|
||||
const dataGridTraceId = uuid().slice(0, 8);
|
||||
|
|
@ -1902,7 +1902,7 @@ const canShowWhereSearch = computed(() => !!props.onExecuteSql && !isResultsCont
|
|||
const canUseWhereSearch = computed(() => !!props.tableMeta && !!props.onExecuteSql && !isResultsContext.value);
|
||||
type DataGridTableMeta = NonNullable<typeof props.tableMeta>;
|
||||
const hiveTableTransactional = ref<boolean | undefined>(undefined);
|
||||
const canEditExistingRows = computed(() => !!props.customSave || canEditExistingTableRows(props.databaseType, hiveTableTransactional.value, props.tableMeta?.primaryKeys ?? []));
|
||||
const canEditExistingRows = computed(() => !!props.customSaveHandler || canEditExistingTableRows(props.databaseType, hiveTableTransactional.value, props.tableMeta?.primaryKeys ?? []));
|
||||
watch(
|
||||
() => [props.databaseType, props.connectionId, props.database, props.tableMeta?.schema, props.tableMeta?.tableName],
|
||||
async () => {
|
||||
|
|
@ -2118,7 +2118,7 @@ const editor = useDataGridEditor({
|
|||
sourceColumns: computed(() => props.sourceColumns),
|
||||
canEditExistingRows,
|
||||
onExecuteSql: computed(() => props.onExecuteSql),
|
||||
customSave: computed(() => props.customSave),
|
||||
customSaveHandler: computed(() => props.customSaveHandler),
|
||||
sql: computed(() => props.sql),
|
||||
searchText,
|
||||
whereFilterInput,
|
||||
|
|
@ -2234,7 +2234,7 @@ const saveActionMode = computed(() =>
|
|||
const saveToolbarState = computed(() =>
|
||||
dataGridSaveToolbarState({
|
||||
editable: props.editable,
|
||||
hasSaveTarget: !!props.tableMeta || !!props.customSave,
|
||||
hasSaveTarget: !!props.tableMeta || !!props.customSaveHandler,
|
||||
hasPendingChanges: hasPendingChanges.value,
|
||||
isSaving: isSaving.value,
|
||||
}),
|
||||
|
|
@ -2242,13 +2242,13 @@ const saveToolbarState = computed(() =>
|
|||
const hasSearchBarSlot = computed(() => !!slots["search-bar"]);
|
||||
const showDataGridTopbar = computed(
|
||||
() =>
|
||||
(useTransaction.value && !!props.editable && (!!props.tableMeta || !!props.customSave)) ||
|
||||
(useTransaction.value && !!props.editable && (!!props.tableMeta || !!props.customSaveHandler)) ||
|
||||
hasLocalColumnFilters.value ||
|
||||
canShowWhereSearch.value ||
|
||||
hasSearchBarSlot.value ||
|
||||
showQueryEditReadyBadge.value ||
|
||||
props.context !== "results" ||
|
||||
(!!props.editable && (!!props.tableMeta || !!props.customSave)) ||
|
||||
(!!props.editable && (!!props.tableMeta || !!props.customSaveHandler)) ||
|
||||
transactionActive.value ||
|
||||
saveToolbarState.value.showActions,
|
||||
);
|
||||
|
|
@ -2616,11 +2616,6 @@ const {
|
|||
isRowSelected,
|
||||
} = selection;
|
||||
|
||||
const selectionSummary = computed(() => {
|
||||
if (hasRowSelection.value) return t("grid.selectedRows", { count: selectedRowCount.value });
|
||||
return t("grid.selectedCells", { count: selectedCellCount.value });
|
||||
});
|
||||
|
||||
const multiRowCount = computed(() => {
|
||||
if (hasRowSelection.value) return selectedRowCount.value;
|
||||
const range = selectedRange.value;
|
||||
|
|
@ -5858,7 +5853,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
"
|
||||
>
|
||||
<div class="data-grid-topbar flex items-stretch relative">
|
||||
<div v-if="useTransaction && editable && (tableMeta || customSave)" class="flex items-center px-2 py-0.5 border-r shrink-0">
|
||||
<div v-if="useTransaction && editable && (tableMeta || customSaveHandler)" class="flex items-center px-2 py-0.5 border-r shrink-0">
|
||||
<Select :model-value="rowStatusFilter" @update:model-value="(value: any) => setRowStatusFilter(String(value))">
|
||||
<SelectTrigger class="h-5 max-w-28 border-0 bg-transparent px-0 py-0 text-xs font-medium text-foreground/70 shadow-none focus-visible:ring-0 data-[state=open]:text-foreground [&_svg]:size-3">
|
||||
<SelectValue :placeholder="t('grid.filterRows')" />
|
||||
|
|
@ -5883,7 +5878,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</template>
|
||||
<template v-if="canShowWhereSearch">
|
||||
<div ref="searchSplitContainerRef" class="flex flex-1 min-w-0">
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0 relative" :class="{ 'border-l': useTransaction && editable && (tableMeta || customSave) }" :style="whereSearchPaneStyle">
|
||||
<div class="flex flex-1 items-center gap-1 px-2 py-0.5 min-w-0 relative" :class="{ 'border-l': useTransaction && editable && (tableMeta || customSaveHandler) }" :style="whereSearchPaneStyle">
|
||||
<Popover v-model:open="filterBuilderOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
|
|
@ -6147,7 +6142,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
{{ t("grid.renderModeHint") }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button v-if="editable && (tableMeta || customSave)" variant="ghost" size="sm" class="h-5 text-xs px-1.5 shrink-0" @click="addRow"> <Plus class="w-3 h-3 mr-1" /> {{ t("grid.addRow") }} </Button>
|
||||
<Button v-if="editable && (tableMeta || customSaveHandler)" variant="ghost" size="sm" class="h-5 text-xs px-1.5 shrink-0" @click="addRow"> <Plus class="w-3 h-3 mr-1" /> {{ t("grid.addRow") }} </Button>
|
||||
<template v-if="saveToolbarState.showActions">
|
||||
<Tooltip v-if="pendingChangeCount > 0">
|
||||
<TooltipTrigger as-child>
|
||||
|
|
@ -6230,20 +6225,11 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
<div v-if="isErrorResult" class="flex-1 flex flex-col items-center justify-center gap-2 px-6 text-center text-destructive">
|
||||
<TriangleAlert class="h-8 w-8 text-destructive/50" aria-hidden="true" />
|
||||
<div class="space-y-1 select-text" @mousedown.stop @click.stop>
|
||||
<div class="text-sm font-medium">{{ t("grid.queryError") }}</div>
|
||||
<div class="text-xs max-w-lg break-all cursor-text text-destructive/80 select-text">{{ errorMessage }}</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||
<Button variant="outline" size="sm" class="h-7 gap-1.5 px-2 text-xs" @click.stop="copyText(errorMessage)">
|
||||
<Copy class="h-3.5 w-3.5" />
|
||||
{{ t("grid.copy") }}
|
||||
</Button>
|
||||
<ErrorBanner v-if="isErrorResult" variant="centered" :message="errorMessage">
|
||||
<template #actions>
|
||||
<slot name="error-actions" :error-message="errorMessage" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ErrorBanner>
|
||||
<div v-else-if="isTransposeMode" class="flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
<div class="h-8 flex items-center gap-2 px-3 border-y shrink-0 bg-muted/20">
|
||||
<Rows3 class="w-3.5 h-3.5 text-muted-foreground" />
|
||||
|
|
@ -7270,11 +7256,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
<span v-if="showTruncationWarning" class="shrink-0 text-amber-500 text-xs">(truncated)</span>
|
||||
<span v-if="!hasData" class="shrink-0">{{ t("grid.rowsAffected", { count: result.affected_rows }) }}</span>
|
||||
<span class="shrink-0">{{ result.execution_time_ms }}ms</span>
|
||||
<span v-if="selectedRowCount > 0 || hasCellSelection" class="min-w-0 truncate text-foreground">
|
||||
{{ selectionSummary }}
|
||||
</span>
|
||||
|
||||
<template v-if="editable && (tableMeta || customSave)">
|
||||
<template v-if="editable && (tableMeta || customSaveHandler)">
|
||||
<span v-if="hasPendingChanges" class="shrink-0 text-foreground">
|
||||
{{ t("grid.pendingChanges", { count: pendingChangeCount }) }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { Copy, TriangleAlert } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
message: string;
|
||||
variant?: "banner" | "centered";
|
||||
title?: string;
|
||||
}>(),
|
||||
{
|
||||
variant: "banner",
|
||||
},
|
||||
);
|
||||
|
||||
const displayTitle = computed(() => props.title ?? t("grid.queryError"));
|
||||
|
||||
async function copy() {
|
||||
try {
|
||||
await copyToClipboard(props.message);
|
||||
toast(t("grid.copied"));
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- banner: 紧凑内联横幅 -->
|
||||
<div v-if="variant === 'banner'" class="flex items-center gap-2 px-3 py-1.5 border-t bg-destructive/10 text-destructive text-xs shrink-0">
|
||||
<span class="flex-1 min-w-0 break-all">{{ message }}</span>
|
||||
<Button variant="ghost" size="icon-sm" class="h-5 w-5 shrink-0 text-destructive/70 hover:text-destructive" :aria-label="t('grid.copy')" @click.stop="copy">
|
||||
<Copy class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- centered: 居中占满 -->
|
||||
<div v-else class="flex-1 flex flex-col items-center justify-center gap-2 px-6 text-center text-destructive">
|
||||
<TriangleAlert class="h-8 w-8 text-destructive/50" aria-hidden="true" />
|
||||
<div class="space-y-1 select-text" @mousedown.stop @click.stop>
|
||||
<div class="text-sm font-medium">{{ displayTitle }}</div>
|
||||
<div class="text-xs max-w-lg break-all cursor-text text-destructive/80 select-text">{{ message }}</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-center gap-2">
|
||||
<Button variant="outline" size="sm" class="h-7 gap-1.5 px-2 text-xs" @click.stop="copy">
|
||||
<Copy class="h-3.5 w-3.5" />
|
||||
{{ t("grid.copy") }}
|
||||
</Button>
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -30,6 +30,11 @@ type GridScrollerRef =
|
|||
scrollToPosition?: (position: number) => void;
|
||||
};
|
||||
|
||||
export interface CustomSaveHandler {
|
||||
save: (changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<void>;
|
||||
preview?: (changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface UseDataGridEditorOptions {
|
||||
result: ComputedRef<{ columns: string[]; rows: CellValue[][] }>;
|
||||
editable: ComputedRef<boolean | undefined>;
|
||||
|
|
@ -48,7 +53,7 @@ export interface UseDataGridEditorOptions {
|
|||
sourceColumns?: ComputedRef<Array<string | undefined> | undefined>;
|
||||
canEditExistingRows?: ComputedRef<boolean>;
|
||||
onExecuteSql: ComputedRef<((sql: string) => Promise<void>) | undefined>;
|
||||
customSave?: ComputedRef<((changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<void>) | undefined>;
|
||||
customSaveHandler?: ComputedRef<CustomSaveHandler | undefined>;
|
||||
sql: ComputedRef<string | undefined>;
|
||||
searchText: Ref<string>;
|
||||
whereFilterInput: Ref<string>;
|
||||
|
|
@ -119,7 +124,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
sourceColumns = computed(() => undefined),
|
||||
canEditExistingRows = computed(() => true),
|
||||
onExecuteSql,
|
||||
customSave,
|
||||
customSaveHandler,
|
||||
sql,
|
||||
searchText,
|
||||
orderByInput,
|
||||
|
|
@ -174,7 +179,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
const isSaving = ref(false);
|
||||
const saveError = ref("");
|
||||
|
||||
const useTransaction = computed(() => editable.value && supportsDataGridTransaction(databaseType.value) && (!!customSave?.value || (!!connectionId.value && !!database.value && !!tableMeta.value)));
|
||||
const useTransaction = computed(() => editable.value && supportsDataGridTransaction(databaseType.value) && (!!customSaveHandler?.value || (!!connectionId.value && !!database.value && !!tableMeta.value)));
|
||||
|
||||
if (hasPendingChanges.value && useTransaction.value) {
|
||||
transactionActive.value = true;
|
||||
|
|
@ -695,9 +700,9 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
isSaving.value = true;
|
||||
const shouldReloadAfterSave = newRows.value.length > 0 || deletedRows.value.size > 0;
|
||||
|
||||
if (customSave?.value) {
|
||||
if (customSaveHandler?.value) {
|
||||
try {
|
||||
await customSave.value({
|
||||
await customSaveHandler.value.save({
|
||||
dirtyRows: dirtyRows.value,
|
||||
newRows: newRows.value,
|
||||
deletedRows: deletedRows.value,
|
||||
|
|
@ -896,8 +901,9 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
isPreviewLoading.value = true;
|
||||
previewStatements.value = [];
|
||||
try {
|
||||
if (customSave?.value) {
|
||||
// customSave doesn't expose SQL — return empty
|
||||
if (customSaveHandler?.value) {
|
||||
const preview = customSaveHandler.value.preview;
|
||||
if (preview) return await preview({ dirtyRows: dirtyRows.value, newRows: newRows.value, deletedRows: deletedRows.value, columns: result.value.columns, rows: result.value.rows });
|
||||
return [];
|
||||
}
|
||||
const stmtOptions = saveStatementOptions();
|
||||
|
|
|
|||
Loading…
Reference in New Issue