@gadmin2n/schematics 0.0.87 → 0.0.89

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.
Files changed (99) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +16 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.codegen +40 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +76 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.web +53 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +219 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +250 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +4 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/dev/postgres/init.sql +12 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +170 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +254 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +8 -7
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +1 -1
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/prismaModels.ts +1 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +39 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/audit.seed.ts +40 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +56 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +39 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/{scripts/sync-data-mngt-pages.ts → seed/data-mngt.seed.ts} +36 -20
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/game.seed.ts +44 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +30 -6
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +130 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-event-trigger.ts +60 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +11 -25
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +108 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +1 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +31 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +31 -2
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +41 -57
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +31 -2
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +309 -1
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +31 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +315 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +31 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +312 -2
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +31 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +317 -1
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +31 -2
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +309 -1
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +31 -2
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +299 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +31 -2
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +307 -1
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +31 -2
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +309 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +205 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.ts +116 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +158 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +110 -1
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.spec.ts +79 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/webhook-signature.util.ts +54 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +34 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +457 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +241 -4
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +34 -2
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +24 -30
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +34 -2
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +36 -36
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +34 -2
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +48 -24
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +312 -3
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +152 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +12 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +79 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +270 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +1 -1
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +23 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +70 -5
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +246 -90
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +20 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +96 -8
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/nginx.conf +74 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/ElementInspector.tsx +18 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/promptGenerator.ts +1 -1
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +1 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -3
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -3
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +4 -1
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +9 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +156 -139
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +14 -2
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +62 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/PublishModal.tsx +4 -6
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +18 -27
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +32 -11
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +48 -61
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas-page/index.tsx +3 -6
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +16 -16
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +28 -35
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -3
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +1 -1
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -1
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +6 -0
  93. package/package.json +1 -1
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +0 -63
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/sync-resources.ts +0 -100
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +0 -302
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.spec.ts +0 -20
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +0 -87
  99. /package/dist/lib/application/files/gadmin2-game-angle-demo/{GRACEFUL-DEPLOYMENT.md → server/GRACEFUL-DEPLOYMENT.md} +0 -0
@@ -279,7 +279,7 @@ function getElement(
279
279
  />
280
280
  </Form.Item>
281
281
  );
282
- } else if (fieldType === 'AutoComplete') {
282
+ } else if ((fieldType as any) === 'AutoComplete') {
283
283
  element = (
284
284
  <Form.Item {...itemProps}>
285
285
  <DynamicAutoComplete
@@ -157,8 +157,8 @@
157
157
  "editCode": "Edit Code",
158
158
  "duplicate": "Duplicate",
159
159
  "delete": "Delete",
160
- "custom": "Ask AI...",
161
- "customAction": "Ask AI...",
160
+ "custom": "Customize...",
161
+ "customAction": "Customize...",
162
162
  "customPlaceholder": "Describe what you want to do with this component",
163
163
  "components": {
164
164
  "NumCard": "Metric Card",
@@ -184,7 +184,7 @@
184
184
  "inline-right": "Inline Right"
185
185
  },
186
186
  "dataSource": {
187
- "label": "Ask AI to change data source...",
187
+ "label": "Change data source...",
188
188
  "placeholder": "Describe the data, e.g.: revenue comparison over last 30 days"
189
189
  },
190
190
  "dataSourceModal": {
@@ -159,8 +159,8 @@
159
159
  "editCode": "编辑代码",
160
160
  "duplicate": "复制",
161
161
  "delete": "删除",
162
- "custom": "让 AI 修改...",
163
- "customAction": "让 AI 修改...",
162
+ "custom": "自定义...",
163
+ "customAction": "自定义...",
164
164
  "customPlaceholder": "描述你想对这个组件做的修改",
165
165
  "components": {
166
166
  "NumCard": "数字卡片",
@@ -186,7 +186,7 @@
186
186
  "inline-right": "行内右对齐"
187
187
  },
188
188
  "dataSource": {
189
- "label": "让 AI 修改数据来源...",
189
+ "label": "修改数据来源...",
190
190
  "placeholder": "描述要展示的数据,例如:近30天游戏收益对比、各部门人数分布、近90天考勤状态统计"
191
191
  },
192
192
  "dataSourceModal": {
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import type { IncomingMessage, ServerResponse } from 'http';
3
4
  import type { Plugin } from 'vite';
4
5
 
5
6
  /**
@@ -15,7 +16,9 @@ export function devShellPlugin(): Plugin {
15
16
  name: 'dev-shell',
16
17
  apply: 'serve',
17
18
  configureServer(server) {
18
- server.middlewares.use(async (req, res, next) => {
19
+ server.middlewares.use(async (rawReq, rawRes, next) => {
20
+ const req = rawReq as IncomingMessage;
21
+ const res = rawRes as ServerResponse;
19
22
  const url = req.url?.split('?')[0] ?? '/';
20
23
 
21
24
  // POST /canvas-patch — agent 将新组件代码通过 HMR 推送给前端画布
@@ -96,6 +96,15 @@ const CanvasEditPage: React.FC = () => {
96
96
  }
97
97
  }, []);
98
98
 
99
+ // ── 30s 自动保存:有未保存变更时,30 秒后自动调用 handleSave ──
100
+ useEffect(() => {
101
+ if (!isDirty) return;
102
+ const timer = setTimeout(() => {
103
+ handleSave();
104
+ }, 30_000);
105
+ return () => clearTimeout(timer);
106
+ }, [isDirty, handleSave]);
107
+
99
108
  // ── Back to list ──
100
109
  const handleBack = useCallback(() => {
101
110
  navigate('/admin/canvas');
@@ -1,6 +1,15 @@
1
1
  import React, { useState, useEffect, useCallback, useRef } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
- import { Button, message, Tag, Spin, Modal, Input } from 'antd';
3
+ import {
4
+ Button,
5
+ Card,
6
+ message,
7
+ Tag,
8
+ Spin,
9
+ Modal,
10
+ Input,
11
+ Typography,
12
+ } from 'antd';
4
13
  import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
5
14
  import {
6
15
  fetchCanvases,
@@ -181,166 +190,174 @@ const CanvasListPage: React.FC = () => {
181
190
  }}
182
191
  >
183
192
  {/* Header */}
184
- <div
185
- style={{
186
- display: 'flex',
187
- justifyContent: 'space-between',
188
- alignItems: 'center',
189
- marginBottom: 24,
190
- }}
191
- >
192
- <h2 style={{ margin: 0, fontSize: 20, fontWeight: 600 }}>
193
- {t('canvas.list.title')}
194
- </h2>
195
- <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
196
- {t('canvas.list.create')}
197
- </Button>
198
- </div>
193
+ <Card style={{ marginBottom: 16 }}>
194
+ <div
195
+ style={{
196
+ display: 'flex',
197
+ justifyContent: 'space-between',
198
+ alignItems: 'center',
199
+ }}
200
+ >
201
+ <Typography.Title level={4} style={{ margin: 0 }}>
202
+ {t('canvas.list.title')}
203
+ </Typography.Title>
204
+ <Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
205
+ {t('canvas.list.create')}
206
+ </Button>
207
+ </div>
208
+ </Card>
199
209
 
200
210
  {/* Grid */}
201
- <div
202
- style={{
203
- display: 'grid',
204
- gridTemplateColumns: 'repeat(3, 1fr)',
205
- gap: 20,
206
- }}
207
- >
208
- {canvases.map((canvas) => (
211
+ <Card>
212
+ {canvases.length > 0 ? (
209
213
  <div
210
- key={canvas.id}
211
214
  style={{
212
- background: '#fff',
213
- borderRadius: 8,
214
- overflow: 'hidden',
215
- border: '1px solid #e5e5e5',
216
- cursor: 'pointer',
217
- transition: 'box-shadow 0.2s',
218
- }}
219
- onMouseEnter={(e) => {
220
- (e.currentTarget as HTMLDivElement).style.boxShadow =
221
- '0 4px 12px rgba(0,0,0,0.1)';
215
+ display: 'grid',
216
+ gridTemplateColumns: 'repeat(3, 1fr)',
217
+ gap: 20,
222
218
  }}
223
- onMouseLeave={(e) => {
224
- (e.currentTarget as HTMLDivElement).style.boxShadow = 'none';
225
- }}
226
- onClick={() => navigate(`/admin/canvas/edit/${canvas.id}`)}
227
219
  >
228
- {/* Thumbnail */}
229
- <div
230
- style={{
231
- height: 180,
232
- overflow: 'hidden',
233
- background: '#f0f0f0',
234
- position: 'relative',
235
- pointerEvents: 'none',
236
- }}
237
- >
238
- {canvas.items.length > 0 ? (
239
- <div
240
- style={{
241
- transform: 'scale(0.28)',
242
- transformOrigin: 'top left',
243
- width: 1200,
244
- height: 640,
245
- }}
246
- >
247
- <IsolatedLivePreview code={buildPreviewCode(canvas)} />
248
- </div>
249
- ) : (
220
+ {canvases.map((canvas) => (
221
+ <div
222
+ key={canvas.id}
223
+ style={{
224
+ background: '#fff',
225
+ borderRadius: 8,
226
+ overflow: 'hidden',
227
+ border: '1px solid #e5e5e5',
228
+ cursor: 'pointer',
229
+ transition: 'box-shadow 0.2s',
230
+ }}
231
+ onMouseEnter={(e) => {
232
+ (e.currentTarget as HTMLDivElement).style.boxShadow =
233
+ '0 4px 12px rgba(0,0,0,0.1)';
234
+ }}
235
+ onMouseLeave={(e) => {
236
+ (e.currentTarget as HTMLDivElement).style.boxShadow = 'none';
237
+ }}
238
+ onClick={() => navigate(`/admin/canvas/edit/${canvas.id}`)}
239
+ >
240
+ {/* Thumbnail */}
250
241
  <div
251
242
  style={{
252
- display: 'flex',
253
- alignItems: 'center',
254
- justifyContent: 'center',
255
- height: '100%',
256
- color: '#bbb',
257
- fontSize: 14,
243
+ height: 180,
244
+ overflow: 'hidden',
245
+ background: '#f0f0f0',
246
+ position: 'relative',
247
+ pointerEvents: 'none',
258
248
  }}
259
249
  >
260
- 空画布
250
+ {canvas.items.length > 0 ? (
251
+ <div
252
+ style={{
253
+ transform: 'scale(0.28)',
254
+ transformOrigin: 'top left',
255
+ width: 1200,
256
+ height: 640,
257
+ }}
258
+ >
259
+ <IsolatedLivePreview code={buildPreviewCode(canvas)} />
260
+ </div>
261
+ ) : (
262
+ <div
263
+ style={{
264
+ display: 'flex',
265
+ alignItems: 'center',
266
+ justifyContent: 'center',
267
+ height: '100%',
268
+ color: '#bbb',
269
+ fontSize: 14,
270
+ }}
271
+ >
272
+ 空画布
273
+ </div>
274
+ )}
261
275
  </div>
262
- )}
263
- </div>
264
276
 
265
- {/* Info */}
266
- <div
267
- style={{ padding: '12px 16px', borderTop: '1px solid #f0f0f0' }}
268
- >
269
- <div
270
- style={{
271
- display: 'flex',
272
- justifyContent: 'space-between',
273
- alignItems: 'center',
274
- marginBottom: 8,
275
- }}
276
- >
277
- <span
277
+ {/* Info */}
278
+ <div
278
279
  style={{
279
- fontWeight: 500,
280
- fontSize: 14,
281
- overflow: 'hidden',
282
- textOverflow: 'ellipsis',
283
- whiteSpace: 'nowrap',
284
- maxWidth: 160,
280
+ padding: '12px 16px',
281
+ borderTop: '1px solid #f0f0f0',
285
282
  }}
286
283
  >
287
- {canvas.name}
288
- </span>
289
- <Tag color={canvas.publishedPageCode ? 'green' : 'default'}>
290
- {canvas.publishedPageCode
291
- ? t('canvas.list.published')
292
- : t('canvas.list.unpublished')}
293
- </Tag>
294
- </div>
295
- <div
296
- style={{
297
- display: 'flex',
298
- justifyContent: 'space-between',
299
- alignItems: 'center',
300
- }}
301
- >
302
- <span style={{ fontSize: 12, color: '#999' }}>
303
- 修改于 {new Date(canvas.updatedAt).toLocaleString('zh-CN')}
304
- </span>
305
- <div style={{ display: 'flex', gap: 4 }}>
306
- <Button
307
- type="text"
308
- size="small"
309
- icon={<EditOutlined />}
310
- onClick={(e) => {
311
- e.stopPropagation();
312
- openRename(canvas);
284
+ <div
285
+ style={{
286
+ display: 'flex',
287
+ justifyContent: 'space-between',
288
+ alignItems: 'center',
289
+ marginBottom: 8,
313
290
  }}
314
- style={{ fontSize: 12, color: '#666' }}
315
291
  >
316
- {t('canvas.list.rename')}
317
- </Button>
318
- <Button
319
- type="text"
320
- size="small"
321
- danger
322
- icon={<DeleteOutlined />}
323
- onClick={(e) => {
324
- e.stopPropagation();
325
- setDeleteConfirm(canvas);
292
+ <span
293
+ style={{
294
+ fontWeight: 500,
295
+ fontSize: 14,
296
+ overflow: 'hidden',
297
+ textOverflow: 'ellipsis',
298
+ whiteSpace: 'nowrap',
299
+ maxWidth: 160,
300
+ }}
301
+ >
302
+ {canvas.name}
303
+ </span>
304
+ <Tag color={canvas.publishedPageCode ? 'green' : 'default'}>
305
+ {canvas.publishedPageCode
306
+ ? t('canvas.list.published')
307
+ : t('canvas.list.unpublished')}
308
+ </Tag>
309
+ </div>
310
+ <div
311
+ style={{
312
+ display: 'flex',
313
+ justifyContent: 'space-between',
314
+ alignItems: 'center',
326
315
  }}
327
- style={{ fontSize: 12 }}
328
316
  >
329
- {t('canvas.list.deleteBtn')}
330
- </Button>
317
+ <span style={{ fontSize: 12, color: '#999' }}>
318
+ 修改于{' '}
319
+ {new Date(canvas.updatedAt).toLocaleString('zh-CN')}
320
+ </span>
321
+ <div style={{ display: 'flex', gap: 4 }}>
322
+ <Button
323
+ type="text"
324
+ size="small"
325
+ icon={<EditOutlined />}
326
+ onClick={(e) => {
327
+ e.stopPropagation();
328
+ openRename(canvas);
329
+ }}
330
+ style={{ fontSize: 12, color: '#666' }}
331
+ >
332
+ {t('canvas.list.rename')}
333
+ </Button>
334
+ <Button
335
+ type="text"
336
+ size="small"
337
+ danger
338
+ icon={<DeleteOutlined />}
339
+ onClick={(e) => {
340
+ e.stopPropagation();
341
+ setDeleteConfirm(canvas);
342
+ }}
343
+ style={{ fontSize: 12 }}
344
+ >
345
+ {t('canvas.list.deleteBtn')}
346
+ </Button>
347
+ </div>
348
+ </div>
331
349
  </div>
332
350
  </div>
333
- </div>
351
+ ))}
334
352
  </div>
335
- ))}
336
- </div>
337
-
338
- {/* Empty state */}
339
- {canvases.length === 0 && (
340
- <div style={{ textAlign: 'center', color: '#999', marginTop: 80 }}>
341
- <p>还没有 Canvas,点击上方按钮新建一个吧</p>
342
- </div>
343
- )}
353
+ ) : (
354
+ <div
355
+ style={{ textAlign: 'center', color: '#999', padding: '80px 0' }}
356
+ >
357
+ <p>还没有 Canvas,点击上方按钮新建一个吧</p>
358
+ </div>
359
+ )}
360
+ </Card>
344
361
 
345
362
  {/* 重命名 Modal */}
346
363
  <Modal
@@ -53,6 +53,13 @@ const COLS = 48;
53
53
  const ROW_HEIGHT = 10;
54
54
  const MARGIN_Y = 8;
55
55
 
56
+ /** 组件库类型 → 画布实际 componentType 映射 */
57
+ const COMPONENT_TYPE_MAP: Record<string, string> = {
58
+ BarChart: 'MultiChart',
59
+ LineChart: 'MultiChart',
60
+ PieChart: 'MultiChart',
61
+ };
62
+
56
63
  // ─── Helpers ─────────────────────────────────────────────────────────────────
57
64
 
58
65
  /** Compute the pixel height a component should fill given a grid row count. */
@@ -234,10 +241,12 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
234
241
  const id = genId();
235
242
  const maxY = layout.reduce((m, l) => Math.max(m, l.y + l.h), 0);
236
243
  const h = def.layout.h;
244
+ // 映射实际 componentType(BarChart/LineChart/PieChart → MultiChart)
245
+ const actualType = COMPONENT_TYPE_MAP[type] ?? type;
237
246
  onBothChange(
238
247
  [
239
248
  ...items,
240
- { id, componentType: type, code: syncCodeHeight(def.code, h) },
249
+ { id, componentType: actualType, code: syncCodeHeight(def.code, h) },
241
250
  ],
242
251
  [
243
252
  ...layout,
@@ -412,10 +421,13 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
412
421
  )
413
422
  : def.code;
414
423
 
424
+ // 映射实际 componentType(BarChart/LineChart/PieChart → MultiChart)
425
+ const actualType = COMPONENT_TYPE_MAP[type] ?? type;
426
+
415
427
  onBothChange(
416
428
  [
417
429
  ...items,
418
- { id, componentType: type, code: syncCodeHeight(baseCode, h) },
430
+ { id, componentType: actualType, code: syncCodeHeight(baseCode, h) },
419
431
  ],
420
432
  [
421
433
  ...cleanLayout,
@@ -55,6 +55,8 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
55
55
  borderBottom: '1px solid #eaeaea',
56
56
  flexShrink: 0,
57
57
  boxShadow: '0 1px 8px rgba(0,0,0,0.04)',
58
+ borderRadius: 8,
59
+ overflow: 'hidden',
58
60
  }}
59
61
  >
60
62
  {/* ── Header row ── */}
@@ -216,6 +218,66 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
216
218
  const def = CANVAS_DEFAULTS[type];
217
219
  const variants = def?.variants ?? [];
218
220
 
221
+ // ── 单 variant:thumbnail 直接可拖拽,跳过 Popover ──
222
+ if (variants.length === 1) {
223
+ return (
224
+ <div
225
+ key={type}
226
+ draggable
227
+ onDragStart={(e) =>
228
+ handleVariantDragStart(e, type, variants[0].code)
229
+ }
230
+ style={{
231
+ display: 'flex',
232
+ flexDirection: 'column',
233
+ alignItems: 'center',
234
+ gap: 8,
235
+ cursor: 'grab',
236
+ userSelect: 'none',
237
+ flexShrink: 0,
238
+ width: 140,
239
+ }}
240
+ >
241
+ <div
242
+ style={{
243
+ border: '1px solid #eaeaea',
244
+ borderRadius: 10,
245
+ overflow: 'hidden',
246
+ background: '#fff',
247
+ transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1)',
248
+ }}
249
+ onMouseEnter={(e) => {
250
+ const el = e.currentTarget;
251
+ el.style.borderColor = '#4361ee';
252
+ el.style.boxShadow = '0 4px 16px rgba(67,97,238,0.12)';
253
+ el.style.transform = 'translateY(-3px) scale(1.03)';
254
+ }}
255
+ onMouseLeave={(e) => {
256
+ const el = e.currentTarget;
257
+ el.style.borderColor = '#eaeaea';
258
+ el.style.boxShadow = 'none';
259
+ el.style.transform = 'translateY(0) scale(1)';
260
+ }}
261
+ >
262
+ <ComponentThumbnail componentType={type} width={140} />
263
+ </div>
264
+ <span
265
+ style={{
266
+ fontSize: 11,
267
+ color: '#888',
268
+ fontWeight: 500,
269
+ letterSpacing: '0.3px',
270
+ textAlign: 'center',
271
+ pointerEvents: 'none',
272
+ }}
273
+ >
274
+ {getComponentLabel(type, t)}
275
+ </span>
276
+ </div>
277
+ );
278
+ }
279
+
280
+ // ── 多 variant:保持 Popover hover 展开 ──
219
281
  const popoverContent = (
220
282
  <div
221
283
  style={{
@@ -3,6 +3,7 @@ import { Modal, Form, Input, TreeSelect, message } from 'antd';
3
3
  import type { SavedCanvas } from './types';
4
4
  import { publishCanvas } from './canvasApi';
5
5
  import { useTranslation } from 'react-i18next';
6
+ import { customRequest } from 'helpers/http';
6
7
 
7
8
  interface RawPage {
8
9
  id: number;
@@ -80,13 +81,10 @@ const PublishModal: React.FC<PublishModalProps> = ({
80
81
  useEffect(() => {
81
82
  if (!open) return;
82
83
  setPagesLoading(true);
83
- fetch('/api/page/findMany', {
84
- method: 'POST',
85
- headers: { 'Content-Type': 'application/json' },
86
- credentials: 'include',
87
- body: JSON.stringify({ where: {}, orderBy: { sortOrder: 'asc' } }),
84
+ customRequest<{ data: RawPage[] }>('page/findMany', 'POST', {
85
+ where: {},
86
+ orderBy: { sortOrder: 'asc' },
88
87
  })
89
- .then((r) => r.json())
90
88
  .then((data) => {
91
89
  const pages: RawPage[] = data?.data ?? [];
92
90
  setTreeData(buildTree(pages, t('canvas.publishModal.rootPage')));
@@ -1,7 +1,6 @@
1
1
  import type { SavedCanvas, CanvasItem } from './types';
2
2
  import type { LayoutItem } from 'react-grid-layout';
3
-
4
- const BASE = '/api/canvas';
3
+ import { customRequest } from 'helpers/http';
5
4
 
6
5
  // 服务端返回的 Canvas 形状(createdAt 是 ISO 字符串)
7
6
  interface CanvasRecord {
@@ -29,31 +28,25 @@ function toSavedCanvas(r: CanvasRecord): SavedCanvas {
29
28
  };
30
29
  }
31
30
 
32
- async function request<T>(url: string, init?: RequestInit): Promise<T> {
33
- const res = await fetch(url, {
34
- headers: { 'Content-Type': 'application/json' },
35
- credentials: 'include',
36
- ...init,
37
- });
38
- if (!res.ok) throw new Error(`Canvas API error: ${res.status}`);
39
- return res.json() as Promise<T>;
40
- }
41
-
42
31
  export async function fetchCanvases(): Promise<SavedCanvas[]> {
43
- const records = await request<CanvasRecord[]>(BASE);
32
+ const records = await customRequest<CanvasRecord[]>('canvas', 'GET');
44
33
  return records.map(toSavedCanvas);
45
34
  }
46
35
 
47
36
  export async function fetchCanvas(id: string): Promise<SavedCanvas | null> {
48
- const record = await request<CanvasRecord | null>(`${BASE}/${id}`);
37
+ const record = await customRequest<CanvasRecord | null>(
38
+ `canvas/${id}`,
39
+ 'GET',
40
+ );
49
41
  if (!record) return null;
50
42
  return toSavedCanvas(record);
51
43
  }
52
44
 
53
45
  export async function createCanvas(name: string): Promise<SavedCanvas> {
54
- const record = await request<CanvasRecord>(BASE, {
55
- method: 'POST',
56
- body: JSON.stringify({ name, items: [], layout: [] }),
46
+ const record = await customRequest<CanvasRecord>('canvas', 'POST', {
47
+ name,
48
+ items: [],
49
+ layout: [],
57
50
  });
58
51
  return toSavedCanvas(record);
59
52
  }
@@ -67,26 +60,24 @@ export async function updateCanvas(
67
60
  >
68
61
  >,
69
62
  ): Promise<void> {
70
- await request(`${BASE}/${id}`, {
71
- method: 'PUT',
72
- body: JSON.stringify(patch),
73
- });
63
+ await customRequest(`canvas/${id}`, 'PUT', patch);
74
64
  }
75
65
 
76
66
  export async function deleteCanvas(id: string): Promise<void> {
77
- await request(`${BASE}/${id}`, { method: 'DELETE' });
67
+ await customRequest(`canvas/${id}`, 'DELETE');
78
68
  }
79
69
 
80
70
  export async function publishCanvas(
81
71
  id: string,
82
72
  dto: { slug: string; pageName: string; parentPageCode?: string },
83
73
  ): Promise<{ success: boolean; pageCode: string }> {
84
- return request(`${BASE}/${id}/publish`, {
85
- method: 'POST',
86
- body: JSON.stringify(dto),
87
- });
74
+ return customRequest<{ success: boolean; pageCode: string }>(
75
+ `canvas/${id}/publish`,
76
+ 'POST',
77
+ dto,
78
+ );
88
79
  }
89
80
 
90
81
  export async function unpublishCanvas(id: string): Promise<void> {
91
- await request(`${BASE}/${id}/unpublish`, { method: 'DELETE' });
82
+ await customRequest(`canvas/${id}/unpublish`, 'DELETE');
92
83
  }