fix(providers): confirm discarding unsaved edits on category switch

Previously confirmDiscardIfDirty only fired for Sheet-driven close paths
(Cancel button, backdrop, escape). Clicking a different provider in the
left rail bypassed the prompt and silently dropped the form changes.
ProviderSheet now exposes the existing guard via an imperative handle,
which the workbench page calls before swapping the active brand.
This commit is contained in:
LTbinglingfeng
2026-05-25 23:52:50 +08:00
parent 1aa99887da
commit 42c0e7be06
2 changed files with 26 additions and 10 deletions
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer';
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
@@ -15,7 +15,7 @@ import type {
OpenAISortBy,
SortDir,
} from './components/OpenAIBrandToolbar';
import { ProviderSheet } from './sheets/ProviderSheet';
import { ProviderSheet, type ProviderSheetHandle } from './sheets/ProviderSheet';
import { useProviderWorkbench } from './useProviderWorkbench';
import type { ProviderBrand, ProviderResource } from './types';
import styles from './ProvidersWorkbenchPage.module.scss';
@@ -81,6 +81,7 @@ export function ProvidersWorkbenchPage() {
mode: 'detail',
resource: null,
});
const sheetRef = useRef<ProviderSheetHandle>(null);
const connected = connectionStatus === 'connected';
const { usageByProvider, refreshRecentRequests } = useProviderRecentRequests({
@@ -372,13 +373,19 @@ export function ProvidersWorkbenchPage() {
groups={groups}
activeBrand={activeGroup.id}
onSelect={(brand) => {
setActiveBrand(brand);
setFilter('');
setOpenaiSelectedModels(new Set());
// 关闭 Sheet 以避免数据错位
if (sheetState.open && sheetState.brand !== brand) {
closeSheet();
}
const isSwitching = sheetState.open && sheetState.brand !== brand;
const proceed = isSwitching && sheetRef.current
? sheetRef.current.confirmDiscardIfDirty()
: Promise.resolve(true);
void proceed.then((ok) => {
if (!ok) return;
setActiveBrand(brand);
setFilter('');
setOpenaiSelectedModels(new Set());
if (isSwitching) {
closeSheet();
}
});
}}
/>
<ProviderResourcePanel
@@ -399,6 +406,7 @@ export function ProvidersWorkbenchPage() {
</div>
<ProviderSheet
ref={sheetRef}
state={sheetState}
onClose={closeSheet}
onSwitchToEdit={() => {
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useId, useState } from 'react';
import { useCallback, useEffect, useId, useImperativeHandle, useState, type Ref } from 'react';
import { useTranslation } from 'react-i18next';
import { Sheet } from '@/components/ui/Sheet';
import { IconLoader2, IconPencil } from '@/components/ui/icons';
@@ -24,6 +24,10 @@ export interface ProviderSheetState {
resource: ProviderResource | null;
}
export interface ProviderSheetHandle {
confirmDiscardIfDirty: () => Promise<boolean>;
}
interface ProviderSheetProps {
state: ProviderSheetState;
onClose: () => void;
@@ -31,6 +35,7 @@ interface ProviderSheetProps {
workbench: UseProviderWorkbenchResult;
onCreated: () => void;
onUpdated: () => void;
ref?: Ref<ProviderSheetHandle>;
}
export function ProviderSheet({
@@ -40,6 +45,7 @@ export function ProviderSheet({
workbench,
onCreated,
onUpdated,
ref,
}: ProviderSheetProps) {
const { t } = useTranslation();
const { showConfirmation } = useNotificationStore();
@@ -79,6 +85,8 @@ export function ProviderSheet({
});
}, [isDirty, isEditingForm, showConfirmation, submitting, t]);
useImperativeHandle(ref, () => ({ confirmDiscardIfDirty }), [confirmDiscardIfDirty]);
const handleCancelClick = useCallback(() => {
void confirmDiscardIfDirty().then((ok) => {
if (ok) onClose();