From 1f391e9ec220c8272cad486b6d637142014814a8 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jun 2026 13:39:07 +0800 Subject: [PATCH] fix(storage): apply driver switch on save and repoint upload records Saving storage settings now activates the selected driver immediately instead of staging it until migration. When the driver changes, existing upload rows are repointed to the new storage_driver so /f/{id} reads use the correct backend. ForDriver can also open non-active drivers from the saved multi-backend config. --- .agent/skills/new-async-task/SKILL.md | 1 - .../admin/files/components/file-list.tsx | 24 +++---- .../files/components/storage-config-tab.tsx | 2 +- .../components/common/file-image-preview.tsx | 62 +++++++++++++++++++ .../components/common/user/file-manager.tsx | 39 ++++++------ internal/apps/admin/system_config/logics.go | 36 +++++++++++ internal/apps/admin/system_config/routers.go | 4 -- .../apps/admin/system_config/routers_test.go | 6 +- internal/storage/storage.go | 7 ++- 9 files changed, 134 insertions(+), 47 deletions(-) create mode 100644 frontend/components/common/file-image-preview.tsx diff --git a/.agent/skills/new-async-task/SKILL.md b/.agent/skills/new-async-task/SKILL.md index 5305e14d..99f2b42e 100644 --- a/.agent/skills/new-async-task/SKILL.md +++ b/.agent/skills/new-async-task/SKILL.md @@ -43,7 +43,6 @@ description: "Wavelet 项目专用:新增或修改 Asynq 异步任务、后台 - 失败返回 error,由任务框架处理状态和重试。 - 不要吞掉关键错误。 - 复杂 SQL 放到 `internal/model/` 或模块内的业务服务层(如 `internal/apps//service.go` 或 `logics.go`)。 -- 新增 Go 文件后检查许可证头,必要时运行 `make license`。 ### 注册 diff --git a/frontend/app/(main)/admin/files/components/file-list.tsx b/frontend/app/(main)/admin/files/components/file-list.tsx index aae424e4..fc217630 100644 --- a/frontend/app/(main)/admin/files/components/file-list.tsx +++ b/frontend/app/(main)/admin/files/components/file-list.tsx @@ -35,7 +35,8 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog" import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle} from "@/components/ui/sheet" -import services, {formatFileSize, getFileUrl} from "@/lib/services" +import {FileImagePreview} from "@/components/common/file-image-preview" +import services, {formatFileSize} from "@/lib/services" import type {Upload as UploadRecord} from "@/lib/services/upload/types" /* ─── 工具函数 ─────────────────────────────────────────── */ @@ -288,16 +289,12 @@ export function FileList() {
{file.mime_type.startsWith("image/") ? ( - // eslint-disable-next-line @next/next/no-img-element - {file.file_name} { - ;(e.currentTarget as HTMLImageElement).style.display = "none" - }} /> ) : ( getFileIcon(file.mime_type, "size-4.5") @@ -406,13 +403,12 @@ export function FileList() { {/* 大图/格式预览 */}
{detailTarget.mime_type.startsWith("image/") ? ( - // eslint-disable-next-line @next/next/no-img-element - {detailTarget.file_name} ) : (
diff --git a/frontend/app/(main)/admin/files/components/storage-config-tab.tsx b/frontend/app/(main)/admin/files/components/storage-config-tab.tsx index b6fdd5a0..29642f35 100644 --- a/frontend/app/(main)/admin/files/components/storage-config-tab.tsx +++ b/frontend/app/(main)/admin/files/components/storage-config-tab.tsx @@ -316,7 +316,7 @@ export function StorageConfigTab() { {config.driver !== query.data?.config.driver && ( - ⚠️ 您已切换存储类型。请先点击“保存配置”保存各存储端的配置凭据,然后点击“开始迁移”手动执行文件迁移。 + ⚠️ 您已切换存储类型。点击「保存配置」将立即切换活动存储引擎,并同步更新已有文件的存储驱动标记。仅在需要复制物理文件时才使用「开始迁移」。 )}
diff --git a/frontend/components/common/file-image-preview.tsx b/frontend/components/common/file-image-preview.tsx new file mode 100644 index 00000000..3564bca2 --- /dev/null +++ b/frontend/components/common/file-image-preview.tsx @@ -0,0 +1,62 @@ +"use client" + +import * as React from "react" +import {ImageOff} from "lucide-react" + +import {cn} from "@/lib/utils" +import {getFileUrl, type ImageQuality} from "@/lib/services/upload/utils" + +type FileImagePreviewProps = { + fileId: string | number + alt: string + quality?: ImageQuality + className?: string + fallbackClassName?: string + variant?: "compact" | "default" +} + +export function FileImagePreview({ + fileId, + alt, + quality = "low", + className, + fallbackClassName, + variant = "default", +}: FileImagePreviewProps) { + const [failed, setFailed] = React.useState(false) + const src = getFileUrl(fileId, quality) + + React.useEffect(() => { + setFailed(false) + }, [fileId, quality]) + + if (!src || failed) { + return ( +
+ + {variant === "default" && ( + 加载失败 + )} +
+ ) + } + + return ( + // eslint-disable-next-line @next/next/no-img-element + {alt} setFailed(true)} + /> + ) +} \ No newline at end of file diff --git a/frontend/components/common/user/file-manager.tsx b/frontend/components/common/user/file-manager.tsx index 8fe47601..15e94b49 100644 --- a/frontend/components/common/user/file-manager.tsx +++ b/frontend/components/common/user/file-manager.tsx @@ -1,23 +1,23 @@ "use client" import * as React from "react" -import {useQuery, useMutation, useQueryClient} from "@tanstack/react-query" +import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query" import { - Upload, - Trash2, + ChevronLeft, + ChevronRight, Download, - Search, - FileText, - FileImage, - FileVideo, - FileAudio, FileArchive, - Loader2, + FileAudio, + FileImage, + FileText, + FileVideo, Globe, + Loader2, Lock, Plus, - ChevronLeft, - ChevronRight + Search, + Trash2, + Upload } from "lucide-react" import {toast} from "sonner" @@ -36,7 +36,8 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog" -import services, {formatFileSize, getFileUrl} from "@/lib/services" +import {FileImagePreview} from "@/components/common/file-image-preview" +import services, {formatFileSize} from "@/lib/services" import type {Upload as UploadRecord} from "@/lib/services/upload/types" /* ─── 工具函数 ─────────────────────────────────────────── */ @@ -238,16 +239,12 @@ export function UserFileManager() { {/* 文件预览/图标区 */}
{file.mime_type.startsWith("image/") ? ( - // eslint-disable-next-line @next/next/no-img-element - {file.file_name} { - ;(e.currentTarget as HTMLImageElement).style.display = "none" - }} + fallbackClassName="min-h-full w-full rounded-md" /> ) : ( getFileIcon(file.mime_type) @@ -283,7 +280,7 @@ export function UserFileManager() {
{formatDate(file.created_at)} - +