"use client"; import React, { Suspense, useMemo } from "react"; import { Canvas } from "@react-three/fiber"; import { Environment, OrbitControls, useGLTF } from "@react-three/drei"; import { AlertCircle, Box, ExternalLink, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; interface Model3DViewerProps { modelUrl?: string | null; previewImageUrl?: string | null; } class ViewerErrorBoundary extends React.Component< { children: React.ReactNode; fallback: React.ReactNode }, { hasError: boolean } > { constructor(props: { children: React.ReactNode; fallback: React.ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } componentDidUpdate(prevProps: { children: React.ReactNode }) { if (prevProps.children !== this.props.children && this.state.hasError) { this.setState({ hasError: false }); } } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } function ModelScene({ url }: { url: string }) { const model = useGLTF(url); const scene = useMemo(() => model.scene.clone(), [model.scene]); return ; } function isGlbOrGltf(url?: string | null) { const value = String(url || "").toLowerCase(); return /\.(gltf|glb)(\?|#|$)/.test(value); } function EmptyState({ previewImageUrl }: { previewImageUrl?: string | null }) { return (
{previewImageUrl ? ( 3D 预览 ) : ( )}

生成完成后可在此区域预览 3D 资产

); } export function Model3DViewer({ modelUrl, previewImageUrl }: Model3DViewerProps) { if (!modelUrl) { return ; } if (!isGlbOrGltf(modelUrl)) { return (

当前文件格式不支持内嵌预览,请在新窗口打开

); } return (

3D 预览加载失败,请尝试在新窗口打开

} > } >
拖拽旋转 / 滚轮缩放
); }