上传
This commit is contained in:
parent
e915a16cb3
commit
31c330e928
@ -1036,11 +1036,21 @@ function ResourceFormDialog({ disabled, form, loading, mode, onClose, onSubmit,
|
||||
const handleAnimationSelected = async (file) => {
|
||||
let detectedMp4Layout = null;
|
||||
if (isMp4File(file)) {
|
||||
try {
|
||||
detectedMp4Layout = await detectMp4AlphaLayout(file);
|
||||
} catch {
|
||||
// 布局解析只负责补充客户端渲染元数据,失败不能把 MP4 变成不可上传格式。
|
||||
detectedMp4Layout = null;
|
||||
}
|
||||
}
|
||||
let detectedProfileCardLayout = null;
|
||||
if (form.resourceType === "profile_card") {
|
||||
try {
|
||||
detectedProfileCardLayout = await detectProfileCardLayout(file);
|
||||
} catch {
|
||||
// 部分 PAG 版本无法被浏览器侧 libpag 解码,但原文件仍可供客户端消费,继续走通用上传。
|
||||
detectedProfileCardLayout = null;
|
||||
}
|
||||
}
|
||||
// 只返回当次素材的检测增量,上传成功后再合并到最新表单,避免异步旧快照覆盖用户新修改。
|
||||
return {
|
||||
|
||||
@ -19,7 +19,6 @@ import { Mp4Preview } from "@/shared/ui/Mp4Preview.jsx";
|
||||
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||
import styles from "./UploadField.module.css";
|
||||
|
||||
const imageAccept = "image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp,.svga,.pag";
|
||||
const pagWasmURL = "/vendor/libpag.wasm";
|
||||
let pagModulePromise;
|
||||
|
||||
@ -51,12 +50,10 @@ export function UploadField({
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [videoPreviewOpen, setVideoPreviewOpen] = useState(false);
|
||||
const isImage = kind === "image";
|
||||
const uploadMode = uploadKind || kind;
|
||||
const useImageUpload = uploadMode === "image";
|
||||
const source = localPreview?.url || value;
|
||||
const assetKind = localPreview?.assetKind || getAssetKind(value, kind);
|
||||
const displayName = localPreview?.name || getDisplayValue(value);
|
||||
const inputAccept = accept !== undefined ? accept : useImageUpload && isImage ? imageAccept : undefined;
|
||||
const inputAccept = accept || undefined;
|
||||
const canPreviewVideo = Boolean(source && assetKind === "mp4");
|
||||
const localPreviewURL = localPreview?.url;
|
||||
const previewMp4Layout = Object.prototype.hasOwnProperty.call(localPreview?.selectionResult || {}, "mp4Layout")
|
||||
@ -115,7 +112,9 @@ export function UploadField({
|
||||
return;
|
||||
}
|
||||
setLocalPreview((previous) => (previous ? { ...previous, selectionResult } : previous));
|
||||
const result = useImageUpload ? await uploadImage(file) : await uploadFile(file);
|
||||
// 图片接口会校验固定的位图格式;资源素材不能被这层校验变成格式白名单,
|
||||
// 因此仅 JPEG/PNG/WebP 走图片接口,其余 PAG/SVGA/GIF/AVIF 或未知格式走通用文件接口。
|
||||
const result = shouldUploadAsImage(file, kind, uploadKind) ? await uploadImage(file) : await uploadFile(file);
|
||||
if (uploadGeneration !== uploadGenerationRef.current) {
|
||||
return;
|
||||
}
|
||||
@ -550,6 +549,20 @@ function getAssetKind(value, kind, contentType = "") {
|
||||
return kind === "image" ? "image" : "file";
|
||||
}
|
||||
|
||||
function shouldUploadAsImage(file, kind, uploadKind) {
|
||||
if (uploadKind !== undefined) {
|
||||
return uploadKind === "image";
|
||||
}
|
||||
if (kind !== "image") {
|
||||
return false;
|
||||
}
|
||||
const extension = getExtension(file?.name);
|
||||
if (extension) {
|
||||
return ["jpg", "jpeg", "png", "webp"].includes(extension);
|
||||
}
|
||||
return ["image/jpeg", "image/png", "image/webp"].includes(String(file?.type || "").toLowerCase());
|
||||
}
|
||||
|
||||
function isRasterImageExtension(extension) {
|
||||
return ["avif", "gif", "jpg", "jpeg", "png", "webp"].includes(extension);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { beforeEach, expect, test, vi } from "vitest";
|
||||
import { downloadResponse } from "@/shared/api/download";
|
||||
import { uploadFile, uploadImage } from "@/shared/api/upload";
|
||||
import { ToastProvider } from "@/shared/ui/ToastProvider.jsx";
|
||||
@ -14,6 +14,10 @@ vi.mock("@/shared/api/download", () => ({
|
||||
downloadResponse: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("file upload field does not restrict picker type and uses generic upload endpoint", async () => {
|
||||
const onChange = vi.fn();
|
||||
const onFileSelected = vi.fn();
|
||||
@ -38,6 +42,40 @@ test("file upload field does not restrict picker type and uses generic upload en
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
|
||||
});
|
||||
|
||||
test("image-style resource field accepts PAG and routes it through generic upload", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { container } = render(
|
||||
<ToastProvider>
|
||||
<UploadField kind="image" label="动效素材" value="" onChange={onChange} />
|
||||
</ToastProvider>,
|
||||
);
|
||||
const input = container.querySelector('input[type="file"]');
|
||||
const file = new File(["pag"], "effect.pag", { type: "application/octet-stream" });
|
||||
|
||||
expect(input).not.toHaveAttribute("accept");
|
||||
fireEvent.change(input, { target: { files: [file] } });
|
||||
|
||||
await waitFor(() => expect(uploadFile).toHaveBeenCalledWith(file));
|
||||
expect(uploadImage).not.toHaveBeenCalled();
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
|
||||
});
|
||||
|
||||
test("standard raster image keeps the strict image upload endpoint", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { container } = render(
|
||||
<ToastProvider>
|
||||
<UploadField kind="image" label="封面" value="" onChange={onChange} />
|
||||
</ToastProvider>,
|
||||
);
|
||||
const file = new File(["png"], "cover.png", { type: "image/png" });
|
||||
|
||||
fireEvent.change(container.querySelector('input[type="file"]'), { target: { files: [file] } });
|
||||
|
||||
await waitFor(() => expect(uploadImage).toHaveBeenCalledWith(file));
|
||||
expect(uploadFile).not.toHaveBeenCalled();
|
||||
expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/images/effect.png");
|
||||
});
|
||||
|
||||
test("mp4 file upload field can open video preview dialog", async () => {
|
||||
const source = "https://media.haiyihy.com/admin/files/gift.mp4?sign=1";
|
||||
const onChange = vi.fn();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user