prd-workflow-cli 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,455 +4,424 @@
4
4
  <head>
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>PRD A2UI 预览器</title>
7
+ <title>PRD A2UI 预览器 - Ant Design</title>
8
+ <!-- React -->
9
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
10
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
11
+ <!-- Ant Design -->
12
+ <link rel="stylesheet" href="https://unpkg.com/antd@5/dist/reset.css">
13
+ <script src="https://unpkg.com/dayjs@1/dayjs.min.js"></script>
14
+ <script src="https://unpkg.com/antd@5/dist/antd.min.js"></script>
15
+ <!-- Icons -->
16
+ <script src="https://unpkg.com/@ant-design/icons@5/dist/index.umd.min.js"></script>
8
17
  <style>
9
- :root {
10
- --bg-color: #f3f4f6;
11
- --panel-bg: #ffffff;
12
- --text-primary: #1f2937;
13
- --border-color: #e5e7eb;
14
- --primary-color: #2563eb;
15
- }
16
-
17
18
  body {
18
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
19
- background-color: var(--bg-color);
20
- color: var(--text-primary);
21
19
  margin: 0;
22
- padding: 20px;
23
- display: flex;
24
- justify-content: center;
25
- }
26
-
27
- #app {
28
- width: 100%;
29
- max-width: 1200px;
30
- }
31
-
32
- /* 基础组件样式 */
33
- .component-page {
34
- display: flex;
35
- flex-direction: column;
36
- gap: 16px;
37
- }
38
-
39
- .component-panel {
40
- background: var(--panel-bg);
41
- border-radius: 8px;
42
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
43
- padding: 20px;
44
- border: 1px solid var(--border-color);
45
- }
46
-
47
- .component-header {
48
- font-size: 1.5rem;
49
- font-weight: 600;
50
- margin-bottom: 16px;
51
- border-bottom: 1px solid var(--border-color);
52
- padding-bottom: 12px;
53
- }
54
-
55
- .component-button {
56
- background-color: var(--primary-color);
57
- color: white;
58
- border: none;
59
- padding: 8px 16px;
60
- border-radius: 6px;
61
- cursor: pointer;
62
- font-size: 0.9rem;
63
- transition: background 0.2s;
64
- }
65
-
66
- .component-button:hover {
67
- background-color: #1d4ed8;
68
- }
69
-
70
- .component-input {
71
- width: 100%;
72
- padding: 8px 12px;
73
- border: 1px solid var(--border-color);
74
- border-radius: 6px;
75
- margin-top: 4px;
20
+ padding: 24px;
21
+ background: #f5f5f5;
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
76
23
  }
77
24
 
78
- .component-label {
79
- display: block;
80
- font-weight: 500;
81
- margin-bottom: 4px;
82
- font-size: 0.9rem;
25
+ #root {
26
+ max-width: 1400px;
27
+ margin: 0 auto;
83
28
  }
84
29
 
85
- .component-group {
30
+ .page-header {
86
31
  margin-bottom: 16px;
87
32
  }
88
33
 
89
- /* 布局组件 */
90
- .layout-row {
34
+ .page-title {
35
+ font-size: 20px;
36
+ font-weight: 600;
37
+ color: rgba(0, 0, 0, 0.88);
38
+ margin: 0;
91
39
  display: flex;
92
- gap: 16px;
93
- flex-wrap: wrap;
94
- }
95
-
96
- .layout-col {
97
- flex: 1;
98
- min-width: 300px;
40
+ align-items: center;
41
+ gap: 8px;
99
42
  }
100
43
 
101
- /* 状态提示 */
102
- #status {
44
+ .status-bar {
103
45
  position: fixed;
104
- bottom: 20px;
105
- right: 20px;
106
- background: #333;
46
+ bottom: 24px;
47
+ right: 24px;
48
+ background: rgba(0, 0, 0, 0.85);
107
49
  color: white;
108
50
  padding: 8px 16px;
109
- border-radius: 20px;
110
- font-size: 0.8rem;
111
- opacity: 0.8;
112
- }
113
-
114
- /* ========== 架构图组件样式 ========== */
115
-
116
- /* 图表容器 */
117
- .component-diagram {
118
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
119
- border-radius: 12px;
120
- padding: 24px;
121
- min-height: 300px;
122
- position: relative;
123
- }
124
-
125
- .diagram-title {
126
- color: white;
127
- font-size: 1.5rem;
128
- font-weight: 600;
129
- margin-bottom: 20px;
130
- text-align: center;
131
- }
132
-
133
- .diagram-content {
51
+ border-radius: 6px;
52
+ font-size: 12px;
134
53
  display: flex;
135
- flex-direction: column;
136
- gap: 20px;
137
54
  align-items: center;
55
+ gap: 8px;
56
+ z-index: 1000;
138
57
  }
139
58
 
140
- /* 模块方框 */
141
- .component-box {
142
- background: white;
143
- border-radius: 8px;
144
- padding: 16px 24px;
145
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
146
- text-align: center;
147
- min-width: 120px;
148
- transition: transform 0.2s, box-shadow 0.2s;
149
- }
150
-
151
- .component-box:hover {
152
- transform: translateY(-2px);
153
- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
59
+ .status-dot {
60
+ width: 6px;
61
+ height: 6px;
62
+ border-radius: 50%;
63
+ background: #52c41a;
154
64
  }
155
65
 
156
- .box-title {
157
- font-weight: 600;
158
- font-size: 1rem;
159
- color: #1f2937;
160
- margin-bottom: 4px;
161
- }
162
-
163
- .box-desc {
164
- font-size: 0.8rem;
165
- color: #6b7280;
166
- }
167
-
168
- /* 分组容器 */
169
- .component-group-diagram {
170
- background: rgba(255, 255, 255, 0.1);
171
- border: 2px dashed rgba(255, 255, 255, 0.3);
172
- border-radius: 12px;
173
- padding: 16px;
174
- width: 100%;
175
- }
176
-
177
- .group-title {
178
- color: rgba(255, 255, 255, 0.9);
179
- font-size: 0.9rem;
180
- font-weight: 500;
181
- margin-bottom: 12px;
182
- }
183
-
184
- .group-content {
66
+ .loading-container {
185
67
  display: flex;
186
- flex-wrap: wrap;
187
- gap: 12px;
188
68
  justify-content: center;
189
- }
190
-
191
- /* 连接线/箭头 */
192
- .component-arrow {
193
- display: flex;
194
69
  align-items: center;
195
- justify-content: center;
196
- color: rgba(255, 255, 255, 0.8);
197
- font-size: 1.5rem;
198
- padding: 8px 0;
199
- }
200
-
201
- .arrow-label {
202
- font-size: 0.75rem;
203
- margin-left: 8px;
204
- color: rgba(255, 255, 255, 0.7);
205
- }
206
-
207
- /* 层级容器 */
208
- .component-layer {
209
- width: 100%;
210
- display: flex;
211
- flex-wrap: wrap;
212
- gap: 12px;
213
- justify-content: center;
214
- padding: 12px 0;
215
- }
216
-
217
- .layer-title {
218
- width: 100%;
219
- text-align: center;
220
- color: rgba(255, 255, 255, 0.7);
221
- font-size: 0.8rem;
222
- margin-bottom: 8px;
70
+ min-height: 400px;
223
71
  }
224
72
  </style>
225
73
  </head>
226
74
 
227
75
  <body>
228
- <div id="app">
229
- <div style="text-align: center; padding: 50px;">
230
- <h2>等待 A2UI 数据...</h2>
231
- <p>请在 CLI 中生成界面数据</p>
232
- </div>
76
+ <div id="root"></div>
77
+ <div class="status-bar">
78
+ <span class="status-dot"></span>
79
+ <span id="status-text">连接中...</span>
233
80
  </div>
234
- <div id="status">连接中...</div>
235
81
 
236
82
  <script>
237
- // A2UI 渲染引擎
238
- const Renderer = {
239
- create(type, props) {
240
- console.log('Creating component:', type, props);
241
- const el = document.createElement('div');
242
- el.className = `component-${type.toLowerCase()}`;
243
-
244
- // 处理通用属性
245
- if (props.id) el.id = props.id;
246
- if (props.style) Object.assign(el.style, props.style);
247
-
248
- // 特定组件渲染逻辑
249
- switch (type) {
250
- case 'Page':
251
- if (props.title) {
252
- const title = document.createElement('div');
253
- title.className = 'component-header'; // 复用 header 样式
254
- title.style.borderBottom = 'none';
255
- title.style.fontSize = '2rem';
256
- title.textContent = props.title;
257
- el.appendChild(title);
258
- }
259
- return el;
83
+ const {
84
+ ConfigProvider, App, Card, Button, Input, Select, Table, Tabs, Tag, Badge,
85
+ Space, Row, Col, Typography, Divider, Alert, Upload, Form, message
86
+ } = antd;
87
+ const { Title, Text, Paragraph } = Typography;
88
+ const { TextArea } = Input;
89
+ const { PlusOutlined, EditOutlined, CopyOutlined, DeleteOutlined, SearchOutlined, ReloadOutlined, UploadOutlined, InboxOutlined } = icons;
90
+ const { Dragger } = Upload;
91
+
92
+ // 渲染器组件
93
+ const A2UIRenderer = ({ data }) => {
94
+ if (!data) {
95
+ return React.createElement('div', { className: 'loading-container' },
96
+ React.createElement(antd.Spin, { size: 'large', tip: '等待 A2UI 数据...' })
97
+ );
98
+ }
260
99
 
261
- case 'Panel':
262
- if (props.title) {
263
- const title = document.createElement('div');
264
- title.className = 'component-header';
265
- title.textContent = props.title;
266
- el.appendChild(title);
267
- }
268
- return el;
100
+ const renderNode = (node) => {
101
+ if (!node) return null;
269
102
 
270
- case 'Button':
271
- const btn = document.createElement('button');
272
- btn.className = 'component-button';
273
- btn.textContent = props.text || 'Button';
274
- return btn;
103
+ const { type, children, ...props } = node;
275
104
 
276
- case 'Input':
277
- const group = document.createElement('div');
278
- group.className = 'component-group';
279
- if (props.label) {
280
- const label = document.createElement('label');
281
- label.className = 'component-label';
282
- label.textContent = props.label;
283
- group.appendChild(label);
284
- }
285
- const input = document.createElement('input');
286
- input.className = 'component-input';
287
- input.placeholder = props.placeholder || '';
288
- group.appendChild(input);
289
- return group;
105
+ switch (type) {
106
+ case 'Page':
107
+ return React.createElement('div', { key: props.id },
108
+ props.title && React.createElement('div', { className: 'page-header' },
109
+ React.createElement('h1', { className: 'page-title' }, '📋 ', props.title)
110
+ ),
111
+ children && children.map((child, i) => renderNode({ ...child, key: i }))
112
+ );
290
113
 
291
- case 'Text':
292
- const p = document.createElement('p');
293
- p.style.margin = '8px 0';
294
- p.textContent = props.content || '';
295
- return p;
114
+ case 'Panel':
115
+ return React.createElement(Card, {
116
+ key: props.key,
117
+ title: props.title,
118
+ extra: props.extra && React.createElement(Space, null,
119
+ props.extra.map((btn, i) =>
120
+ React.createElement(Button, {
121
+ key: i,
122
+ type: btn.variant === 'primary' ? 'primary' : 'default',
123
+ icon: btn.icon === 'plus' ? React.createElement(PlusOutlined) : null
124
+ }, btn.text || btn)
125
+ )
126
+ ),
127
+ style: { marginBottom: 16 }
128
+ }, children && children.map((child, i) => renderNode({ ...child, key: i })));
296
129
 
297
130
  case 'Row':
298
- el.className = 'layout-row';
299
- return el;
131
+ return React.createElement(Row, { key: props.key, gutter: 16 },
132
+ children && children.map((child, i) => renderNode({ ...child, key: i }))
133
+ );
300
134
 
301
135
  case 'Col':
302
- el.className = 'layout-col';
303
- return el;
136
+ return React.createElement(Col, { key: props.key, flex: 1 },
137
+ children && children.map((child, i) => renderNode({ ...child, key: i }))
138
+ );
139
+
140
+ case 'Input':
141
+ return React.createElement(Form.Item, {
142
+ key: props.key,
143
+ label: props.label,
144
+ required: props.required,
145
+ style: { marginBottom: 16 }
146
+ }, React.createElement(Input, { placeholder: props.placeholder }));
147
+
148
+ case 'Textarea':
149
+ return React.createElement(Form.Item, {
150
+ key: props.key,
151
+ label: props.label,
152
+ style: { marginBottom: 16 }
153
+ }, React.createElement(TextArea, { placeholder: props.placeholder, rows: props.rows || 4 }));
154
+
155
+ case 'Select':
156
+ const options = (props.options || []).map(opt => ({
157
+ value: typeof opt === 'string' ? opt : opt.value,
158
+ label: typeof opt === 'string' ? opt : opt.label
159
+ }));
160
+ return React.createElement(Form.Item, {
161
+ key: props.key,
162
+ label: props.label,
163
+ style: { marginBottom: 16 }
164
+ }, React.createElement(Select, {
165
+ placeholder: '请选择',
166
+ options: options,
167
+ style: { width: '100%' }
168
+ }));
304
169
 
305
- // ========== 架构图组件 ==========
170
+ case 'Button':
171
+ const btnType = props.variant === 'secondary' ? 'default' :
172
+ props.variant === 'danger' ? 'primary' :
173
+ props.variant === 'link' ? 'link' : 'primary';
174
+ const danger = props.variant === 'danger';
175
+ return React.createElement(Button, {
176
+ key: props.key,
177
+ type: btnType,
178
+ danger: danger,
179
+ icon: props.icon === 'plus' ? React.createElement(PlusOutlined) :
180
+ props.icon === 'search' ? React.createElement(SearchOutlined) : null,
181
+ style: { marginRight: 8 }
182
+ }, props.text);
306
183
 
184
+ case 'Text':
185
+ return React.createElement(Text, { key: props.key, style: { display: 'block', marginBottom: 8 } }, props.content);
186
+
187
+ case 'Tabs':
188
+ const items = (props.items || []).map((item, i) => ({
189
+ key: String(i),
190
+ label: item
191
+ }));
192
+ return React.createElement(Tabs, { key: props.key, items: items, style: { marginBottom: 16 } });
193
+
194
+ case 'Table':
195
+ const columns = (props.columns || []).map(col => {
196
+ const column = {
197
+ key: col.key || col,
198
+ dataIndex: col.key || col,
199
+ title: col.title || col
200
+ };
201
+
202
+ if (col.type === 'link') {
203
+ column.render = (text) => React.createElement('a', null, text);
204
+ } else if (col.type === 'badge') {
205
+ column.render = (text) => {
206
+ const colorMap = col.variantMap || {};
207
+ const color = colorMap[text] === 'success' ? 'green' :
208
+ colorMap[text] === 'warning' ? 'orange' :
209
+ colorMap[text] === 'danger' ? 'red' : 'blue';
210
+ return React.createElement(Tag, { color: color }, text);
211
+ };
212
+ } else if (col.type === 'status') {
213
+ column.render = (text) => React.createElement(Badge, {
214
+ status: text === '已发布' ? 'success' : 'default',
215
+ text: text
216
+ });
217
+ } else if (col.type === 'actions') {
218
+ column.render = () => React.createElement(Space, null,
219
+ React.createElement('a', null, '编辑'),
220
+ React.createElement('a', null, '复制'),
221
+ React.createElement('a', { style: { color: '#ff4d4f' } }, '删除')
222
+ );
223
+ }
224
+ return column;
225
+ });
226
+
227
+ const dataSource = (props.data || []).map((row, i) => ({ ...row, key: i }));
228
+
229
+ return React.createElement(Table, {
230
+ key: props.key,
231
+ columns: columns,
232
+ dataSource: dataSource,
233
+ pagination: false,
234
+ size: 'middle'
235
+ });
236
+
237
+ case 'Badge':
238
+ const tagColor = props.variant === 'success' ? 'green' :
239
+ props.variant === 'warning' ? 'orange' :
240
+ props.variant === 'danger' ? 'red' : 'blue';
241
+ return React.createElement(Tag, { key: props.key, color: tagColor }, props.text);
242
+
243
+ case 'Card':
244
+ return React.createElement(Card, {
245
+ key: props.key,
246
+ size: 'small',
247
+ style: { marginBottom: 12 }
248
+ },
249
+ React.createElement(Row, { justify: 'space-between', align: 'middle' },
250
+ React.createElement(Col, null,
251
+ React.createElement(Space, { direction: 'vertical', size: 0 },
252
+ React.createElement(Text, { strong: true }, props.title),
253
+ props.tags && React.createElement(Space, { size: 4 },
254
+ props.tags.map((tag, i) =>
255
+ React.createElement(Tag, { key: i, color: tag.variant === 'success' ? 'green' : 'blue' }, tag.text)
256
+ )
257
+ ),
258
+ props.status && React.createElement(Badge, {
259
+ status: props.status === '已发布' ? 'success' : 'default',
260
+ text: props.status
261
+ })
262
+ )
263
+ ),
264
+ props.actions && React.createElement(Col, null,
265
+ React.createElement(Space, null,
266
+ props.actions.map((action, i) =>
267
+ React.createElement(Button, { key: i, size: 'small' }, action.text || action)
268
+ )
269
+ )
270
+ )
271
+ )
272
+ );
273
+
274
+ case 'Upload':
275
+ return React.createElement(Dragger, { key: props.key },
276
+ React.createElement('p', { className: 'ant-upload-drag-icon' },
277
+ React.createElement(InboxOutlined)
278
+ ),
279
+ React.createElement('p', { className: 'ant-upload-text' }, props.text || '点击或拖拽文件到此处上传'),
280
+ React.createElement('p', { className: 'ant-upload-hint' }, props.hint || '支持单个或批量上传')
281
+ );
282
+
283
+ case 'Divider':
284
+ return React.createElement(Divider, { key: props.key });
285
+
286
+ case 'Alert':
287
+ const alertType = props.variant === 'danger' ? 'error' : props.variant || 'info';
288
+ return React.createElement(Alert, {
289
+ key: props.key,
290
+ type: alertType,
291
+ message: props.content || props.text,
292
+ showIcon: true,
293
+ style: { marginBottom: 16 }
294
+ });
295
+
296
+ // 架构图组件
307
297
  case 'Diagram':
308
- el.className = 'component-diagram';
309
- if (props.title) {
310
- const title = document.createElement('div');
311
- title.className = 'diagram-title';
312
- title.textContent = props.title;
313
- el.appendChild(title);
314
- }
315
- const content = document.createElement('div');
316
- content.className = 'diagram-content';
317
- el.appendChild(content);
318
- el._content = content; // 用于子元素渲染
319
- return el;
298
+ return React.createElement('div', {
299
+ key: props.key,
300
+ style: {
301
+ background: 'linear-gradient(135deg, #1677ff 0%, #722ed1 100%)',
302
+ borderRadius: 8,
303
+ padding: 32,
304
+ minHeight: 300
305
+ }
306
+ },
307
+ props.title && React.createElement('div', {
308
+ style: { color: 'white', fontSize: 18, fontWeight: 600, textAlign: 'center', marginBottom: 24 }
309
+ }, props.title),
310
+ React.createElement('div', {
311
+ style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }
312
+ }, children && children.map((child, i) => renderNode({ ...child, key: i })))
313
+ );
320
314
 
321
315
  case 'Box':
322
- el.className = 'component-box';
323
- if (props.color) {
324
- el.style.borderLeft = `4px solid ${props.color}`;
325
- }
326
- const boxTitle = document.createElement('div');
327
- boxTitle.className = 'box-title';
328
- boxTitle.textContent = props.title || props.text || '';
329
- el.appendChild(boxTitle);
330
- if (props.desc) {
331
- const boxDesc = document.createElement('div');
332
- boxDesc.className = 'box-desc';
333
- boxDesc.textContent = props.desc;
334
- el.appendChild(boxDesc);
335
- }
336
- return el;
316
+ return React.createElement(Card, {
317
+ key: props.key,
318
+ size: 'small',
319
+ style: {
320
+ minWidth: 120,
321
+ textAlign: 'center',
322
+ borderLeft: props.color ? `3px solid ${props.color}` : undefined
323
+ }
324
+ },
325
+ React.createElement(Text, { strong: true }, props.title),
326
+ props.desc && React.createElement('div', null,
327
+ React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, props.desc)
328
+ )
329
+ );
337
330
 
338
331
  case 'Arrow':
339
- el.className = 'component-arrow';
340
- const arrowSymbol = props.direction === 'up' ? '↑' :
332
+ const arrow = props.direction === 'up' ? '↑' :
341
333
  props.direction === 'left' ? '←' :
342
334
  props.direction === 'right' ? '→' : '↓';
343
- el.textContent = arrowSymbol;
344
- if (props.label) {
345
- const arrowLabel = document.createElement('span');
346
- arrowLabel.className = 'arrow-label';
347
- arrowLabel.textContent = props.label;
348
- el.appendChild(arrowLabel);
349
- }
350
- return el;
335
+ return React.createElement('div', {
336
+ key: props.key,
337
+ style: { color: 'white', fontSize: 24, textAlign: 'center' }
338
+ }, arrow, props.label && React.createElement('span', { style: { fontSize: 12, marginLeft: 8 } }, props.label));
351
339
 
352
340
  case 'Layer':
353
- el.className = 'component-layer';
354
- if (props.title) {
355
- const layerTitle = document.createElement('div');
356
- layerTitle.className = 'layer-title';
357
- layerTitle.textContent = props.title;
358
- el.appendChild(layerTitle);
359
- }
360
- return el;
341
+ return React.createElement('div', {
342
+ key: props.key,
343
+ style: { display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center', width: '100%' }
344
+ },
345
+ props.title && React.createElement('div', {
346
+ style: { width: '100%', textAlign: 'center', color: 'rgba(255,255,255,0.8)', fontSize: 12, marginBottom: 8 }
347
+ }, props.title),
348
+ children && children.map((child, i) => renderNode({ ...child, key: i }))
349
+ );
361
350
 
362
351
  case 'DiagramGroup':
363
- el.className = 'component-group-diagram';
364
- if (props.title) {
365
- const groupTitle = document.createElement('div');
366
- groupTitle.className = 'group-title';
367
- groupTitle.textContent = props.title;
368
- el.appendChild(groupTitle);
369
- }
370
- const groupContent = document.createElement('div');
371
- groupContent.className = 'group-content';
372
- el.appendChild(groupContent);
373
- el._content = groupContent;
374
- return el;
352
+ return React.createElement('div', {
353
+ key: props.key,
354
+ style: {
355
+ background: 'rgba(255,255,255,0.1)',
356
+ border: '1px dashed rgba(255,255,255,0.3)',
357
+ borderRadius: 8,
358
+ padding: 16,
359
+ width: '100%'
360
+ }
361
+ },
362
+ props.title && React.createElement('div', {
363
+ style: { color: 'rgba(255,255,255,0.9)', fontSize: 14, marginBottom: 12 }
364
+ }, props.title),
365
+ React.createElement('div', {
366
+ style: { display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center' }
367
+ }, children && children.map((child, i) => renderNode({ ...child, key: i })))
368
+ );
375
369
 
376
370
  default:
377
- console.warn('Unknown component type:', type);
378
- el.textContent = `Unknown Component: ${type}`;
379
- el.style.border = '1px dashed red';
380
- el.style.padding = '10px';
381
- el.style.color = 'red';
382
- return el;
383
- }
384
- },
385
-
386
- // 递归渲染树
387
- renderNode(node, container) {
388
- if (!node) return;
389
-
390
- try {
391
- const el = this.create(node.type || 'Unknown', node);
392
-
393
- // 容器只有在非叶子节点时才去递归渲染 children
394
- // Input, Button, Text, Box, Arrow 等通常不包含 children
395
- const containerTypes = ['Page', 'Panel', 'Row', 'Col', 'Diagram', 'Layer', 'DiagramGroup'];
396
- if (containerTypes.includes(node.type)) {
397
- if (node.children && Array.isArray(node.children)) {
398
- // Diagram 和 DiagramGroup 有特殊的内容容器
399
- const targetContainer = el._content || el;
400
- node.children.forEach(child => this.renderNode(child, targetContainer));
401
- }
402
- }
403
-
404
- container.appendChild(el);
405
- } catch (err) {
406
- console.error('Render Error:', err, node);
407
- const errEl = document.createElement('div');
408
- errEl.style.color = 'red';
409
- errEl.textContent = `渲染错误: ${err.message}`;
410
- container.appendChild(errEl);
371
+ console.warn('Unknown component:', type);
372
+ return React.createElement(Alert, {
373
+ key: props.key,
374
+ type: 'warning',
375
+ message: `未知组件: ${type}`,
376
+ style: { marginBottom: 16 }
377
+ });
411
378
  }
412
- },
413
-
414
- render(data) {
415
- console.log('Rendering data:', data);
416
- const app = document.getElementById('app');
417
- app.innerHTML = ''; // 清空当前视图
379
+ };
418
380
 
419
- if (!data) {
420
- app.innerHTML = '<div style="text-align: center; padding: 50px;">无数据</div>';
421
- return;
381
+ return React.createElement(ConfigProvider, {
382
+ theme: {
383
+ token: {
384
+ colorPrimary: '#1677ff',
385
+ borderRadius: 6
386
+ }
422
387
  }
423
-
424
- this.renderNode(data, app);
425
- }
388
+ }, renderNode(data));
426
389
  };
427
390
 
428
- // 模拟数据轮询 (真实环境应该用 WebSocket 或 SSE)
391
+ // 主应用
392
+ let currentData = null;
429
393
  let lastDataStr = '';
430
394
 
395
+ function render() {
396
+ const root = ReactDOM.createRoot(document.getElementById('root'));
397
+ root.render(React.createElement(A2UIRenderer, { data: currentData }));
398
+ }
399
+
431
400
  async function checkUpdate() {
432
401
  try {
433
402
  const res = await fetch('/ui.json');
434
403
  if (res.ok) {
435
404
  const data = await res.json();
436
- const currentDataStr = JSON.stringify(data);
405
+ const dataStr = JSON.stringify(data);
437
406
 
438
- // 只有数据变化时才重新渲染
439
- if (currentDataStr !== lastDataStr) {
440
- lastDataStr = currentDataStr;
441
- Renderer.render(data);
407
+ if (dataStr !== lastDataStr) {
408
+ lastDataStr = dataStr;
409
+ currentData = data;
410
+ render();
442
411
  }
443
412
 
444
- document.getElementById('status').textContent = '已连接: 实时预览中';
445
- document.getElementById('status').style.background = '#059669';
413
+ document.getElementById('status-text').textContent = '已连接';
414
+ document.querySelector('.status-dot').style.background = '#52c41a';
446
415
  }
447
416
  } catch (e) {
448
- console.log('Waiting for data...', e);
449
- document.getElementById('status').textContent = `连接失败: ${e.message}`;
450
- document.getElementById('status').style.background = '#dc2626';
417
+ document.getElementById('status-text').textContent = '连接失败';
418
+ document.querySelector('.status-dot').style.background = '#ff4d4f';
451
419
  }
452
420
  }
453
421
 
454
422
  // 初始化
455
- setInterval(checkUpdate, 1000); // 每秒轮询一次
423
+ render();
424
+ setInterval(checkUpdate, 1000);
456
425
  checkUpdate();
457
426
  </script>
458
427
  </body>