prd-workflow-cli 1.2.6 → 1.3.4

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,440 @@
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);
154
- }
155
-
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;
59
+ .status-dot {
60
+ width: 6px;
61
+ height: 6px;
62
+ border-radius: 50%;
63
+ background: #52c41a;
182
64
  }
183
65
 
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
+ );
304
139
 
305
- // ========== 架构图组件 ==========
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
+ }));
306
169
 
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);
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;
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
+ });
383
378
  }
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);
411
- }
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
- const res = await fetch('/ui.json');
402
+ // 获取数据
403
+ const res = await fetch('/ui.json' + window.location.search);
434
404
  if (res.ok) {
435
405
  const data = await res.json();
436
- const currentDataStr = JSON.stringify(data);
406
+ const dataStr = JSON.stringify(data);
437
407
 
438
- // 只有数据变化时才重新渲染
439
- if (currentDataStr !== lastDataStr) {
440
- lastDataStr = currentDataStr;
441
- Renderer.render(data);
408
+ if (dataStr !== lastDataStr) {
409
+ lastDataStr = dataStr;
410
+ currentData = data;
411
+ render();
442
412
  }
443
413
 
444
- document.getElementById('status').textContent = '已连接: 实时预览中';
445
- document.getElementById('status').style.background = '#059669';
414
+ document.getElementById('status-text').textContent = '已连接';
415
+ document.querySelector('.status-dot').style.background = '#52c41a';
416
+
417
+ // 如果有 file 参数,显示当前文件
418
+ const params = new URLSearchParams(window.location.search);
419
+ if (params.get('file')) {
420
+ document.title = `预览: ${params.get('file')} - PRD A2UI`;
421
+ // 可以在页面上也显示一下
422
+ const statusBar = document.querySelector('.status-bar');
423
+ if (!document.getElementById('status-file-info')) {
424
+ const fileInfo = document.createElement('span');
425
+ fileInfo.id = 'status-file-info';
426
+ fileInfo.style.marginLeft = '10px';
427
+ fileInfo.textContent = `| 文件: ${params.get('file')}`;
428
+ statusBar.appendChild(fileInfo);
429
+ }
430
+ }
446
431
  }
447
432
  } catch (e) {
448
- console.log('Waiting for data...', e);
449
- document.getElementById('status').textContent = `连接失败: ${e.message}`;
450
- document.getElementById('status').style.background = '#dc2626';
433
+ document.getElementById('status-text').textContent = '连接失败';
434
+ document.querySelector('.status-dot').style.background = '#ff4d4f';
451
435
  }
452
436
  }
453
437
 
454
438
  // 初始化
455
- setInterval(checkUpdate, 1000); // 每秒轮询一次
439
+ render();
440
+ setInterval(checkUpdate, 1000);
456
441
  checkUpdate();
457
442
  </script>
458
443
  </body>