You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中Papaya.js无法渲染多NIfTI文件容器问题求助

解决Next.js中多Papaya.js组件仅单个显示NIfTI文件的问题

问题根源

  • 每个组件重复加载jQuery、Papaya.js及样式,导致脚本冲突或全局对象覆盖
  • 调用全局window.papaya.Container.startPapaya()会一次性扫描所有容器,但多次调用可能仅处理第一个,或后续调用失效
  • 重复添加样式/脚本标签会引发资源重复加载冲突

解决方案步骤

步骤1:全局仅加载一次Papaya依赖资源

创建全局初始化组件,确保jQuery、Papaya.js和样式只加载一次,避免重复加载冲突:

"use client";
import { useEffect, useState } from "react";

declare global {
  interface Window {
    papaya: any;
    jQuery: any;
  }
}

export default function PapayaGlobalLoader() {
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    if (isLoaded || window.papaya) return;

    // 加载样式(检查是否已存在)
    const loadStyles = () => {
      if (!document.querySelector('link[href="/papaya.css"]')) {
        const papayaStyles = document.createElement("link");
        papayaStyles.href = "/papaya.css";
        papayaStyles.rel = "stylesheet";
        document.head.appendChild(papayaStyles);
      }

      if (!document.querySelector('link[href*="font-awesome/6.5.1"]')) {
        const fontAwesomeStyles = document.createElement("link");
        fontAwesomeStyles.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css";
        fontAwesomeStyles.rel = "stylesheet";
        document.head.appendChild(fontAwesomeStyles);
      }
    };

    // 加载脚本(确保依赖顺序)
    const loadScripts = () => {
      if (!window.jQuery) {
        const jqueryScript = document.createElement("script");
        jqueryScript.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js";
        jqueryScript.type = "text/javascript";
        jqueryScript.async = false;
        jqueryScript.onload = loadPapaya;
        jqueryScript.onerror = (err) => console.error("加载jQuery失败:", err);
        document.body.appendChild(jqueryScript);
      } else {
        loadPapaya();
      }
    };

    const loadPapaya = () => {
      if (!window.papaya) {
        const papayaScript = document.createElement("script");
        papayaScript.src = "/papaya1.js";
        papayaScript.type = "text/javascript";
        papayaScript.async = false;
        papayaScript.onload = () => {
          console.log("Papaya.js全局加载完成");
          setIsLoaded(true);
        };
        papayaScript.onerror = (err) => console.error("加载Papaya.js失败:", err);
        document.body.appendChild(papayaScript);
      } else {
        setIsLoaded(true);
      }
    };

    loadStyles();
    loadScripts();
  }, [isLoaded]);

  return isLoaded ? null : <div className="text-white">加载医学影像组件...</div>;
}

在根Layout中引入该组件,确保全局仅加载一次:

// app/layout.tsx
import PapayaGlobalLoader from "./PapayaGlobalLoader";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh-CN">
      <body>
        <PapayaGlobalLoader />
        {children}
      </body>
    </html>
  );
}

步骤2:重构通用Papaya展示组件

无需创建多个仅函数名不同的组件,用一个通用组件单独初始化每个容器:

"use client";
import { useEffect, useRef, useState } from "react";

declare global {
  interface Window {
    papaya: any;
  }
}

interface PapayaViewerProps {
  imageUrl: string | null;
}

export default function PapayaViewer({ imageUrl }: PapayaViewerProps) {
  const papayaContainerRef = useRef<HTMLDivElement>(null);
  const [isInitialized, setIsInitialized] = useState(false);

  useEffect(() => {
    const checkPapayaReady = () => {
      if (window.papaya && papayaContainerRef.current && !isInitialized && imageUrl) {
        // 生成唯一容器ID
        const containerId = `papaya-container-${Math.random().toString(36).slice(2, 10)}`;
        papayaContainerRef.current.id = containerId;

        // 单独初始化当前容器
        const params = {
          images: [imageUrl],
          expandable: true,
        };
        window.papaya.Container.createContainer(containerId, params);
        setIsInitialized(true);
        console.log(`Papaya容器 ${containerId} 初始化完成`, imageUrl);
      } else if (!window.papaya) {
        setTimeout(checkPapayaReady, 100);
      }
    };

    checkPapayaReady();

    // 组件卸载时清理实例
    return () => {
      if (window.papaya && papayaContainerRef.current) {
        const container = window.papaya.Container.getContainerById(papayaContainerRef.current.id);
        container?.destroy();
      }
    };
  }, [imageUrl, isInitialized]);

  return <div ref={papayaContainerRef} className="papaya" style={{ width: "400px", height: "400px" }}></div>;
}

步骤3:主页面中使用通用组件

直接复用PapayaViewer组件,传入不同的图片地址即可:

"use client";
import { useRouter } from "next/navigation";
import PapayaViewer from "./PapayaViewer";
import { Breadcrumb, Card } from "antd";
import { ArrowLeftOutlined } from "@ant-design/icons";

export default function ScannerPage() {
  const router = useRouter();
  const items = [{ title: "首页" }, { title: "扫描器" }, { title: "影像查看" }];

  return (
    <>
      <Breadcrumb items={items} />
      <div className="flex justify-center items-start h-full bg-[#212D48] pt-8">
        <div className="flex flex-col items-start max-w-6xl w-full mt-4">
          <div className="flex items-center space-x-2">
            <ArrowLeftOutlined
              className="text-6xl cursor-pointer text-[#736A6A]"
              style={{ paddingBottom: "100px" }}
              onClick={() => router.push("/scanner")}
            />
            <Card
              title="Name:"
              className="w-64 rounded-none"
              bordered={false}
              style={{ background: "#5147CE", color: "white" }}
            >
              <p>Height: 180 cm</p>
              <p>Sex: Male</p>
              <p>Blood Type: O+</p>
            </Card>
          </div>
          <div className="flex justify-between w-full mt-8">
            <div className="flex space-x-4">
              <div className="flex flex-col w-auto h-auto">
                <h3 className="text-white mb-2">Title 1</h3>
                <PapayaViewer imageUrl="/pd_test.gz" />
              </div>
              <div className="flex flex-col w-auto h-auto">
                <h3 className="text-white mb-2">Title 2</h3>
                <PapayaViewer imageUrl="/uploaded.nii.gz" />
              </div>
              <div className="flex flex-col w-auto h-auto">
                <h3 className="text-white mb-2">Title 3</h3>
                <PapayaViewer imageUrl="/uploaded.nii.gz" />
              </div>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

关键修复点

  • 避免重复加载资源:全局仅加载一次依赖,防止脚本冲突
  • 单独初始化容器:使用createContainer()替代全局startPapaya(),确保每个容器都被正确初始化
  • 组件复用:同一组件可渲染不同影像,减少冗余代码
  • 内存泄漏防护:添加组件卸载时的实例销毁逻辑

内容的提问来源于stack exchange,提问作者user25966063

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:54:59