-
- {label}
- {assetKindLabel(assetKind, isImage)}
-
-
-
-
-
{displayName || "未上传"}
-
-
-
-
-
-
- );
+ );
}
function AssetPreview({ assetKind, isImage, src }) {
- if (!src) {
+ if (!src) {
+ return (
+
+ {isImage ? : }
+
+ );
+ }
+ if (assetKind === "svga") {
+ return
;
+ }
+ if (assetKind === "pag") {
+ return
;
+ }
+ if (assetKind === "image" || isImage) {
+ return
;
+ }
return (
-
- {isImage ? : }
-
+
+
+
);
- }
- if (assetKind === "svga") {
- return
;
- }
- if (assetKind === "pag") {
- return
;
- }
- if (assetKind === "image" || isImage) {
- return
;
- }
- return (
-
-
-
- );
}
function RasterAssetPreview({ src }) {
- const [failed, setFailed] = useState(false);
+ const [failed, setFailed] = useState(false);
- useEffect(() => {
- setFailed(false);
- }, [src]);
+ useEffect(() => {
+ setFailed(false);
+ }, [src]);
- if (failed) {
- return (
-
-
-
- );
- }
+ if (failed) {
+ return (
+
+
+
+ );
+ }
- return

setFailed(true)} />;
+ return

setFailed(true)} />;
}
function SVGAAssetPreview({ src }) {
- const containerRef = useRef(null);
- const [failed, setFailed] = useState(false);
+ const containerRef = useRef(null);
+ const [failed, setFailed] = useState(false);
- useEffect(() => {
- let cancelled = false;
- const container = containerRef.current;
- let player;
- setFailed(false);
+ useEffect(() => {
+ let cancelled = false;
+ const container = containerRef.current;
+ let player;
+ setFailed(false);
- async function load() {
- try {
- const module = await import("svgaplayerweb");
- const SVGA = module.default || module;
- if (cancelled || !container) {
- return;
- }
- container.innerHTML = "";
- player = new SVGA.Player(container);
- player.loops = 0;
- player.clearsAfterStop = false;
- player.setContentMode("AspectFit");
- const parser = new SVGA.Parser(container);
- parser.load(
- src,
- (videoItem) => {
- if (cancelled) {
- return;
+ async function load() {
+ try {
+ const module = await import("svgaplayerweb");
+ const SVGA = module.default || module;
+ if (cancelled || !container) {
+ return;
+ }
+ container.innerHTML = "";
+ player = new SVGA.Player(container);
+ player.loops = 0;
+ player.clearsAfterStop = false;
+ player.setContentMode("AspectFit");
+ const parser = new SVGA.Parser(container);
+ parser.load(
+ src,
+ (videoItem) => {
+ if (cancelled) {
+ return;
+ }
+ player.setVideoItem(videoItem);
+ player.startAnimation();
+ },
+ () => {
+ if (!cancelled) {
+ setFailed(true);
+ }
+ },
+ );
+ } catch {
+ if (!cancelled) {
+ setFailed(true);
+ }
}
- player.setVideoItem(videoItem);
- player.startAnimation();
- },
- () => {
- if (!cancelled) {
- setFailed(true);
+ }
+
+ load();
+
+ return () => {
+ cancelled = true;
+ if (player) {
+ try {
+ player.stopAnimation(true);
+ player.clear();
+ } catch {
+ // Ignore player cleanup errors from partially loaded assets.
+ }
}
- }
- );
- } catch {
- if (!cancelled) {
- setFailed(true);
- }
- }
- }
+ if (container) {
+ container.innerHTML = "";
+ }
+ };
+ }, [src]);
- load();
-
- return () => {
- cancelled = true;
- if (player) {
- try {
- player.stopAnimation(true);
- player.clear();
- } catch {
- // Ignore player cleanup errors from partially loaded assets.
- }
- }
- if (container) {
- container.innerHTML = "";
- }
- };
- }, [src]);
-
- return (
-
-
- {failed ? SVGA 预览失败 : null}
-
- );
+ return (
+
+
+ {failed ? SVGA 预览失败 : null}
+
+ );
}
function PAGAssetPreview({ src }) {
- const canvasRef = useRef(null);
- const [failed, setFailed] = useState(false);
+ const canvasRef = useRef(null);
+ const [failed, setFailed] = useState(false);
- useEffect(() => {
- let cancelled = false;
- let pagFile;
- let pagView;
- setFailed(false);
+ useEffect(() => {
+ let cancelled = false;
+ let pagFile;
+ let pagView;
+ setFailed(false);
- async function load() {
- try {
- const PAG = await getPAGModule();
- const buffer = await fetch(src).then((response) => response.arrayBuffer());
- pagFile = await PAG.PAGFile.load(buffer);
- if (cancelled || !canvasRef.current) {
- return;
+ async function load() {
+ try {
+ const PAG = await getPAGModule();
+ const buffer = await fetch(src).then((response) => response.arrayBuffer());
+ pagFile = await PAG.PAGFile.load(buffer);
+ if (cancelled || !canvasRef.current) {
+ return;
+ }
+ canvasRef.current.width = pagFile.width();
+ canvasRef.current.height = pagFile.height();
+ pagView = await PAG.PAGView.init(pagFile, canvasRef.current, { useScale: true });
+ if (!pagView) {
+ throw new Error("PAGView init failed");
+ }
+ pagView.setRepeatCount(0);
+ await pagView.play();
+ } catch {
+ if (!cancelled) {
+ setFailed(true);
+ }
+ }
}
- canvasRef.current.width = pagFile.width();
- canvasRef.current.height = pagFile.height();
- pagView = await PAG.PAGView.init(pagFile, canvasRef.current, { useScale: true });
- if (!pagView) {
- throw new Error("PAGView init failed");
- }
- pagView.setRepeatCount(0);
- await pagView.play();
- } catch {
- if (!cancelled) {
- setFailed(true);
- }
- }
- }
- load();
+ load();
- return () => {
- cancelled = true;
- if (pagView) {
- pagView.destroy();
- }
- if (pagFile?.destroy) {
- pagFile.destroy();
- }
- };
- }, [src]);
+ return () => {
+ cancelled = true;
+ if (pagView) {
+ pagView.destroy();
+ }
+ if (pagFile?.destroy) {
+ pagFile.destroy();
+ }
+ };
+ }, [src]);
- return (
-
-
- {failed ? PAG 预览失败 : null}
-
- );
+ return (
+
+
+ {failed ? PAG 预览失败 : null}
+
+ );
}
async function getPAGModule() {
- if (!pagModulePromise) {
- pagModulePromise = import("libpag").then(({ PAGInit }) => PAGInit({ locateFile: () => pagWasmURL }));
- }
- return pagModulePromise;
+ if (!pagModulePromise) {
+ pagModulePromise = import("libpag").then(({ PAGInit }) => PAGInit({ locateFile: () => pagWasmURL }));
+ }
+ return pagModulePromise;
}
function getDisplayValue(value) {
- if (!value) {
- return "";
- }
- const raw = String(value);
- try {
- const url = new URL(raw);
- const name = url.pathname.split("/").filter(Boolean).pop();
- return name ? decodeURIComponent(name) : raw;
- } catch {
- return raw;
- }
+ if (!value) {
+ return "";
+ }
+ const raw = String(value);
+ try {
+ const url = new URL(raw);
+ const name = url.pathname.split("/").filter(Boolean).pop();
+ return name ? decodeURIComponent(name) : raw;
+ } catch {
+ return raw;
+ }
}
function getAssetKind(value, kind, contentType = "") {
- const extension = getExtension(value);
- if (extension === "svga") {
- return "svga";
- }
- if (extension === "pag") {
- return "pag";
- }
- if (isRasterImageExtension(extension)) {
- return "image";
- }
- const normalizedType = String(contentType || "").toLowerCase();
- if (normalizedType.includes("svga")) {
- return "svga";
- }
- if (normalizedType.includes("pag")) {
- return "pag";
- }
- if (isRasterImageContentType(normalizedType)) {
- return "image";
- }
- return kind === "image" ? "image" : "file";
+ const extension = getExtension(value);
+ if (extension === "svga") {
+ return "svga";
+ }
+ if (extension === "pag") {
+ return "pag";
+ }
+ if (isRasterImageExtension(extension)) {
+ return "image";
+ }
+ const normalizedType = String(contentType || "").toLowerCase();
+ if (normalizedType.includes("svga")) {
+ return "svga";
+ }
+ if (normalizedType.includes("pag")) {
+ return "pag";
+ }
+ if (isRasterImageContentType(normalizedType)) {
+ return "image";
+ }
+ return kind === "image" ? "image" : "file";
}
function isRasterImageExtension(extension) {
- return ["avif", "gif", "jpg", "jpeg", "png", "webp"].includes(extension);
+ return ["avif", "gif", "jpg", "jpeg", "png", "webp"].includes(extension);
}
function isRasterImageContentType(contentType) {
- return ["image/avif", "image/gif", "image/jpeg", "image/png", "image/webp"].includes(contentType);
+ return ["image/avif", "image/gif", "image/jpeg", "image/png", "image/webp"].includes(contentType);
}
function getExtension(value) {
- if (!value) {
- return "";
- }
- const raw = String(value);
- const pathname = safePathname(raw);
- const name = pathname.split("/").filter(Boolean).pop() || raw;
- const cleanName = name.split("?")[0].split("#")[0];
- const dotIndex = cleanName.lastIndexOf(".");
- return dotIndex >= 0 ? cleanName.slice(dotIndex + 1).toLowerCase() : "";
+ if (!value) {
+ return "";
+ }
+ const raw = String(value);
+ const pathname = safePathname(raw);
+ const name = pathname.split("/").filter(Boolean).pop() || raw;
+ const cleanName = name.split("?")[0].split("#")[0];
+ const dotIndex = cleanName.lastIndexOf(".");
+ return dotIndex >= 0 ? cleanName.slice(dotIndex + 1).toLowerCase() : "";
}
function safePathname(value) {
- try {
- return new URL(value).pathname;
- } catch {
- return value;
- }
+ try {
+ return new URL(value).pathname;
+ } catch {
+ return value;
+ }
}
function assetKindLabel(assetKind, isImage) {
- if (assetKind === "image") {
- return "图片";
- }
- if (assetKind === "svga") {
- return "SVGA";
- }
- if (assetKind === "pag") {
- return "PAG";
- }
- return isImage ? "图片" : "文件";
+ if (assetKind === "image") {
+ return "图片";
+ }
+ if (assetKind === "svga") {
+ return "SVGA";
+ }
+ if (assetKind === "pag") {
+ return "PAG";
+ }
+ return isImage ? "图片" : "文件";
}
diff --git a/src/shared/ui/UploadField.test.jsx b/src/shared/ui/UploadField.test.jsx
index 0ddfb59..af36bdc 100644
--- a/src/shared/ui/UploadField.test.jsx
+++ b/src/shared/ui/UploadField.test.jsx
@@ -5,28 +5,30 @@ import { ToastProvider } from "@/shared/ui/ToastProvider.jsx";
import { UploadField } from "./UploadField.jsx";
vi.mock("@/shared/api/upload", () => ({
- uploadFile: vi.fn(async () => ({ url: "https://media.haiyihy.com/admin/files/effect.bin" })),
- uploadImage: vi.fn(async () => ({ url: "https://media.haiyihy.com/admin/images/effect.png" }))
+ uploadFile: vi.fn(async () => ({ url: "https://media.haiyihy.com/admin/files/effect.bin" })),
+ uploadImage: vi.fn(async () => ({ url: "https://media.haiyihy.com/admin/images/effect.png" })),
}));
test("file upload field does not restrict picker type and uses generic upload endpoint", async () => {
- const onChange = vi.fn();
- const { container } = render(
-
-
-
- );
- const input = container.querySelector('input[type="file"]');
+ const onChange = vi.fn();
+ const onFileSelected = vi.fn();
+ const { container } = render(
+
+
+ ,
+ );
+ const input = container.querySelector('input[type="file"]');
- expect(input).not.toHaveAttribute("accept");
+ expect(input).not.toHaveAttribute("accept");
- fireEvent.change(input, {
- target: {
- files: [new File(["effect"], "effect.bin", { type: "application/octet-stream" })]
- }
- });
+ fireEvent.change(input, {
+ target: {
+ files: [new File(["effect"], "effect.bin", { type: "application/octet-stream" })],
+ },
+ });
- await waitFor(() => expect(uploadFile).toHaveBeenCalledTimes(1));
- expect(uploadImage).not.toHaveBeenCalled();
- expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
+ await waitFor(() => expect(uploadFile).toHaveBeenCalledTimes(1));
+ expect(onFileSelected).toHaveBeenCalledWith(expect.objectContaining({ name: "effect.bin" }));
+ expect(uploadImage).not.toHaveBeenCalled();
+ expect(onChange).toHaveBeenCalledWith("https://media.haiyihy.com/admin/files/effect.bin");
});
diff --git a/var/admin-dev.pid b/var/admin-dev.pid
index 0a67ee7..1aa4350 100644
--- a/var/admin-dev.pid
+++ b/var/admin-dev.pid
@@ -1 +1 @@
-99664
+91618