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)} - +