import { useMemo, useRef, useState } from "react"; import { App, Button, Modal, Progress } from "antd"; import { StatusBadge } from "@/components/ui/base/badges"; import { FileImage, UploadCloud, X } from "lucide-react"; import { ASSET_CATEGORY_OPTIONS, type AssetCategory } from "@/lib/asset-category"; import { readImageMeta } from "@/lib/image-utils"; import { uploadImage } from "@/services/image-storage"; import { localSavedRemotePendingMessage, saveRemoteUserDataNow } from "@/services/user-data-sync"; import { flushAssetStorePersistence, useAssetStore } from "@/stores/use-asset-store"; import type { AssetFolder } from "@/services/api/user-data"; import { Select } from "@/components/ui/base/select"; type BatchItem = { id: string; file: File; status: "queued" | "uploading" | "done" | "error"; error?: string; percent?: number }; export function AssetBatchUploadModal({ open, defaultFolderId, folders, onClose, onComplete }: { open: boolean; defaultFolderId: string; folders: AssetFolder[]; onClose: () => void; onComplete: () => Promise }) { const { message } = App.useApp(); const addAsset = useAssetStore((state) => state.addAsset); const [items, setItems] = useState([]); const [category, setCategory] = useState("material"); const [folderId, setFolderId] = useState(defaultFolderId); const [tags, setTags] = useState([]); const [uploading, setUploading] = useState(false); const inputRef = useRef(null); const doneCount = items.filter((item) => item.status === "done").length; const failedItems = items.filter((item) => item.status === "error"); const hasFiles = items.length > 0; const folderOptions = useMemo(() => [{ label: "未分类", value: "" }, ...folders.map((folder) => ({ label: folder.name, value: folder.id }))], [folders]); const chooseFiles = (files: File[]) => { const images = files.filter((file) => file.type.startsWith("image/")); if (!images.length) { message.warning("请选择图片文件"); return; } setItems((current) => [...current, ...images.map((file) => ({ id: `${file.name}-${file.lastModified}-${Math.random()}`, file, status: "queued" as const }))]); }; const uploadBatch = async () => { const pending = items.filter((item) => item.status === "queued" || item.status === "error"); if (!pending.length) return; setUploading(true); let cursor = 0; const worker = async () => { while (cursor < pending.length) { const item = pending[cursor++]; setItems((current) => current.map((entry) => entry.id === item.id ? { ...entry, status: "uploading", percent: 10, error: undefined } : entry)); try { const uploaded = await uploadImage(item.file); const meta = await readImageMeta(uploaded.url).catch(() => ({ width: uploaded.width, height: uploaded.height, mimeType: uploaded.mimeType })); addAsset({ kind: "image", title: item.file.name.replace(/\.[^.]+$/, ""), category, folderId: folderId || undefined, coverUrl: uploaded.url, tags, source: "批量上传", metadata: { source: "manual-batch" }, data: { dataUrl: uploaded.url, storageKey: uploaded.storageKey, width: meta.width || uploaded.width, height: meta.height || uploaded.height, bytes: uploaded.bytes, mimeType: uploaded.mimeType } }); setItems((current) => current.map((entry) => entry.id === item.id ? { ...entry, status: "done", percent: 100 } : entry)); } catch (error) { setItems((current) => current.map((entry) => entry.id === item.id ? { ...entry, status: "error", error: error instanceof Error ? error.message : "上传失败" } : entry)); } } }; await Promise.all(Array.from({ length: Math.min(4, pending.length) }, () => worker())); await flushAssetStorePersistence(); try { await saveRemoteUserDataNow(); } catch (error) { message.warning(localSavedRemotePendingMessage("部分素材已保存在本地", error)); } setUploading(false); await onComplete(); }; const close = () => { if (uploading) return; setItems([]); setTags([]); setFolderId(defaultFolderId); onClose(); }; return
; }