Confirm before deleting creation works

This commit is contained in:
FengLee
2026-05-13 20:01:18 +08:00
parent 244439f71f
commit 6650f5c6fc
3 changed files with 48 additions and 8 deletions

View File

@@ -70,7 +70,7 @@ Use this guide when the user reports behavior. Start from the symptom row, inspe
| Symptom | Check Files | What To Verify |
| --- | --- | --- |
| History missing after generation or login/account switch | `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, create panel component | History POST, `works` insert, URL not data URL except reverse prompt placeholder, and `miaojing_auth_updated` triggers a fresh server fetch. |
| Detail delete removes only local history or record reappears after refresh | `src/components/creation-detail-dialog.tsx`, `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, `src/components/profile/creation-history-tab.tsx` | Logged-in deletion should call `DELETE /api/creation-history?id=...` first, then refresh local history from the server. Check bearer token availability and route ownership filter (`id` + `user_id`). |
| Detail delete removes only local history, skips confirmation, or record reappears after refresh | `src/components/creation-detail-dialog.tsx`, `src/components/ui/alert-dialog.tsx`, `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, `src/components/profile/creation-history-tab.tsx` | The detail action is labeled `删除作品` and must open a confirmation dialog warning that deletion cannot be recovered. Logged-in deletion should call `DELETE /api/creation-history?id=...` first, then refresh local history from the server. Check bearer token availability and route ownership filter (`id` + `user_id`). |
| Published work not in gallery | `src/lib/creation-history-store.ts`, `src/app/api/gallery/publish/route.ts`, `src/app/api/gallery/route.ts`, `src/app/gallery/page.tsx` | `is_public = true`, `status = completed`, media copied to gallery folder, filters. |
| Imported gallery images do not render after production data import | `src/app/api/admin/data-export/route.ts`, `src/app/api/admin/data-import/route.ts`, `src/lib/local-storage.ts`, `src/app/api/local-storage/[...path]/route.ts`, DB `works.result_url` | New exports should include `_media`; import should persist media to local storage. If using an older export without `_media`, DB rows alone cannot recreate missing `/api/local-storage/*` files. |
| Gallery delete does not remove public item | `src/app/api/gallery/route.ts`, admin UI route using it | DELETE unpublishes by setting `is_public = false`, not hard delete. |

View File

@@ -59,7 +59,7 @@ Use this document to jump directly to code before broad searching.
| Style presets | `src/components/create/style-preset-selector.tsx`, `src/lib/style-presets-client.ts`, `src/app/api/style-presets/route.ts`, `src/lib/style-preset-store.ts`, `src/lib/model-config.ts` | Style presets are stored in `image_style_presets`, seeded from defaults, sorted by `usage_count`, and incremented from image generation jobs. |
| Loading/error panels | `src/components/create/generation-loading-panel.tsx`, `src/components/create/generation-task-list.tsx`, `src/components/create/generation-error-panel.tsx` | Shared generation status UI. `generation-task-list` keeps multiple active job cards constrained to the results column. |
| Creation reuse drafts | `src/lib/creation-reuse.ts`, `src/app/create/page.tsx` | Shared localStorage/event bridge used by detail and reverse-prompt actions to prefill create panels. `/create?type=...` changes the active tab after navigation, so callers can route directly to text-to-image or image-to-image. |
| Lightbox/fullscreen/detail actions | `src/components/lightbox.tsx`, `src/components/fullscreen-preview.tsx`, `src/components/creation-detail-dialog.tsx`, `src/components/image-metadata-badge.tsx` | Preview, copy, download, share, reuse config, edit output, and delete record. Image previews show actual natural resolution and computed aspect ratio in the upper-right metadata badge. `BareImagePreview` is the no-container overlay for uploaded reference image previews. |
| Lightbox/fullscreen/detail actions | `src/components/lightbox.tsx`, `src/components/fullscreen-preview.tsx`, `src/components/creation-detail-dialog.tsx`, `src/components/image-metadata-badge.tsx` | Preview, copy, download, share, reuse config, edit output, and delete work. Delete work must use a confirmation dialog warning that deletion cannot be recovered before calling the server delete path. Image previews show actual natural resolution and computed aspect ratio in the upper-right metadata badge. `BareImagePreview` is the no-container overlay for uploaded reference image previews. |
## Generation System

View File

@@ -12,6 +12,16 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Download, Copy, FileSearch, ImageOff, Film, ImageIcon, Share2, CheckCircle2, Maximize2, RotateCcw, PencilLine, Trash2 } from 'lucide-react';
@@ -128,6 +138,7 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
const [mediaAspectRatio, setMediaAspectRatio] = useState<number | null>(null);
const [viewportSize, setViewportSize] = useState({ width: 1280, height: 900 });
const [deleting, setDeleting] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
useEffect(() => {
if (record) {
@@ -257,7 +268,7 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
toast.success('已填入图生图');
};
const handleDeleteRecord = async () => {
const handleDeleteWork = async () => {
if (deleting) return;
setDeleting(true);
try {
@@ -266,8 +277,9 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
} else {
await deleteCreationRecord(record.id);
}
setDeleteConfirmOpen(false);
onClose();
toast.success('记录已删除');
toast.success('作品已删除');
} catch (error) {
toast.error(error instanceof Error ? error.message : '删除失败,请重试');
} finally {
@@ -275,6 +287,32 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
}
};
const deleteConfirmDialog = (
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleting}></AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
disabled={deleting}
onClick={(event) => {
event.preventDefault();
void handleDeleteWork();
}}
>
{deleting ? '删除中' : '确认删除'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
if (isReversePromptRecord) {
const displayCreatedAt = new Date(record.createdAt).toLocaleString('zh-CN');
const referenceImage = record.referenceImage && !isPlaceholder(record.referenceImage) ? record.referenceImage : null;
@@ -360,11 +398,11 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
<Button
variant="destructive"
className="h-10 min-w-[102px] gap-1.5 px-3 text-sm font-semibold"
onClick={handleDeleteRecord}
onClick={() => setDeleteConfirmOpen(true)}
disabled={deleting}
>
<Trash2 className="h-3.5 w-3.5" />
{deleting ? '删除中' : '删除记录'}
{deleting ? '删除中' : '删除作品'}
</Button>
</div>
</div>
@@ -378,6 +416,7 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
open={!!fullscreenSrc}
onClose={() => setFullscreenSrc(null)}
/>
{deleteConfirmDialog}
</>
);
}
@@ -684,11 +723,11 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
<Button
variant="destructive"
className="h-10 min-w-[102px] gap-1.5 px-3 text-sm font-semibold"
onClick={handleDeleteRecord}
onClick={() => setDeleteConfirmOpen(true)}
disabled={deleting}
>
<Trash2 className="h-3.5 w-3.5" />
{deleting ? '删除中' : '删除记录'}
{deleting ? '删除中' : '删除作品'}
</Button>
<Button className="h-10 min-w-[102px] gap-1.5 px-3 text-sm font-semibold" onClick={handleDownload} disabled={isPlaceholderUrl}>
<Download className="h-3.5 w-3.5" />
@@ -727,6 +766,7 @@ export function CreationDetailDialog({ record, open, onClose, onPublishChange, o
open={!!fullscreenSrc}
onClose={() => setFullscreenSrc(null)}
/>
{deleteConfirmDialog}
</>
);
}