| |
| |
| |
| |
| 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) { |
| |
| setHtmlUrl(response.html_url); |
| } else { |
| setError(response.message || '生成失败'); |
| } |
| } catch (e) { |
| setError(e.message || '请求失败'); |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| |
| 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; |
|
|