agent / frontend /src /components /plugins /Visualization3D.jsx
samlax12's picture
Upload 142 files
73801d9 verified
/**
* 3D 可视化插件组件
* 后端生成 Plotly HTML,前端通过 iframe 加载
*/
import { useState, useEffect } from 'react';
import { Button, Spin, Typography, Modal, Empty } from 'antd';
import {
ExpandOutlined,
ReloadOutlined
} from '@ant-design/icons';
import api from '../../services/api';
const { Text } = Typography;
const Visualization3D = ({ code = '' }) => {
const [htmlUrl, setHtmlUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [fullscreen, setFullscreen] = useState(false);
const generate = async (codeToRun) => {
const targetCode = codeToRun || code;
if (!targetCode) return;
setLoading(true);
setError(null);
try {
const response = await api.post('/visualization/3d-surface', { code: targetCode });
if (response.success) {
// html_url 是相对于后端的路径,需要加上后端地址
setHtmlUrl(response.html_url);
} else {
setError(response.message || '生成失败');
}
} catch (e) {
setError(e.message || '请求失败');
} finally {
setLoading(false);
}
};
// 当 code 变化时自动生成
useEffect(() => {
if (code) generate(code);
}, [code]);
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', gap: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Text strong style={{ fontSize: '13px' }}>3D 可视化</Text>
<div>
{htmlUrl && (
<Button
size="small"
icon={<ExpandOutlined />}
onClick={() => setFullscreen(true)}
style={{ marginRight: '8px' }}
>
全屏
</Button>
)}
<Button
size="small"
icon={<ReloadOutlined />}
onClick={() => generate()}
disabled={!code || loading}
>
重新生成
</Button>
</div>
</div>
<div style={{
flex: 1,
borderRadius: '6px',
border: '1px solid rgba(0,0,0,0.1)',
overflow: 'hidden',
position: 'relative',
background: '#fff',
minHeight: '300px'
}}>
{loading ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<Spin tip="正在生成3D图形..."><div style={{ height: 80 }} /></Spin>
</div>
) : error ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<Empty description={<Text type="danger">{error}</Text>} />
</div>
) : htmlUrl ? (
<iframe
src={htmlUrl}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
border: 'none',
}}
title="3D Visualization"
/>
) : (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<Empty description="等待生成3D图形..." />
</div>
)}
</div>
{/* 全屏模态框 */}
<Modal
open={fullscreen}
onCancel={() => setFullscreen(false)}
footer={null}
width="90vw"
style={{ top: '5vh' }}
styles={{ body: { height: '80vh', padding: 0 } }}
destroyOnClose
>
{htmlUrl && (
<iframe
src={htmlUrl}
style={{ width: '100%', height: '80vh', border: 'none' }}
title="3D Visualization Fullscreen"
/>
)}
</Modal>
</div>
);
};
export default Visualization3D;