File size: 3,867 Bytes
ad74240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73801d9
ad74240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * 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;