@gadmin2n/schematics 0.0.111 → 0.0.113

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.
@@ -1,5 +1,5 @@
1
- import React, { useCallback } from 'react';
2
- import { Button, Tooltip, Popover } from 'antd';
1
+ import React from 'react';
2
+ import { Button, Tooltip } from 'antd';
3
3
  import { useIsAgentAllowed } from 'config/agentAllowed';
4
4
  import {
5
5
  SaveOutlined,
@@ -8,10 +8,7 @@ import {
8
8
  EyeOutlined,
9
9
  RobotOutlined,
10
10
  } from '@ant-design/icons';
11
- import ComponentThumbnail from './ComponentThumbnail';
12
- import { CANVAS_COMPONENTS, CANVAS_DEFAULTS } from './canvasDefaults';
13
11
  import { useTranslation } from 'react-i18next';
14
- import { getComponentLabel, getVariantLabel } from './canvasI18n';
15
12
 
16
13
  interface CanvasToolbarProps {
17
14
  isDirty: boolean;
@@ -39,19 +36,6 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
39
36
  const { t } = useTranslation();
40
37
  const isAgentAllowed = useIsAgentAllowed();
41
38
 
42
- const handleVariantDragStart = useCallback(
43
- (e: React.DragEvent, componentType: string, variantCode: string) => {
44
- e.dataTransfer.setData('componentType', componentType);
45
- e.dataTransfer.setData('variantCode', variantCode);
46
- e.dataTransfer.setData(
47
- `x-component-type/${componentType.toLowerCase()}`,
48
- '',
49
- );
50
- e.dataTransfer.effectAllowed = 'copy';
51
- },
52
- [],
53
- );
54
-
55
39
  return (
56
40
  <div
57
41
  style={{
@@ -63,7 +47,6 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
63
47
  overflow: 'hidden',
64
48
  }}
65
49
  >
66
- {/* ── Header row ── */}
67
50
  <div
68
51
  style={{
69
52
  height: 52,
@@ -105,16 +88,11 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
105
88
  </span>
106
89
  </div>
107
90
 
108
- {/* 中间: 占位(保持左右对称布局) */}
109
- <div />
110
-
111
- {/* 右侧: 保存 + 发布 */}
91
+ {/* 右侧: AI 配置 + 保存 + 预览 + 发布 */}
112
92
  <div
113
93
  style={{
114
- flex: 1,
115
94
  display: 'flex',
116
95
  alignItems: 'center',
117
- justifyContent: 'flex-end',
118
96
  gap: 10,
119
97
  }}
120
98
  >
@@ -204,220 +182,6 @@ const CanvasToolbar: React.FC<CanvasToolbarProps> = ({
204
182
  )}
205
183
  </div>
206
184
  </div>
207
-
208
- {/* ── Thumbnail row (only in dev) ── */}
209
- {isAgentAllowed && (
210
- <div
211
- style={{
212
- display: 'flex',
213
- alignItems: 'center',
214
- gap: 24,
215
- padding: '16px 24px 20px',
216
- overflowX: 'auto',
217
- overflowY: 'hidden',
218
- borderTop: '1px solid #f0f0f0',
219
- }}
220
- >
221
- {CANVAS_COMPONENTS.map((type) => {
222
- const def = CANVAS_DEFAULTS[type];
223
- const variants = def?.variants ?? [];
224
-
225
- // ── 单 variant:thumbnail 直接可拖拽,跳过 Popover ──
226
- if (variants.length === 1) {
227
- return (
228
- <div
229
- key={type}
230
- draggable
231
- onDragStart={(e) =>
232
- handleVariantDragStart(e, type, variants[0].code)
233
- }
234
- style={{
235
- display: 'flex',
236
- flexDirection: 'column',
237
- alignItems: 'center',
238
- gap: 8,
239
- cursor: 'grab',
240
- userSelect: 'none',
241
- flexShrink: 0,
242
- width: 140,
243
- }}
244
- >
245
- <div
246
- style={{
247
- border: '1px solid #eaeaea',
248
- borderRadius: 10,
249
- overflow: 'hidden',
250
- background: '#fff',
251
- transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1)',
252
- }}
253
- onMouseEnter={(e) => {
254
- const el = e.currentTarget;
255
- el.style.borderColor = '#4361ee';
256
- el.style.boxShadow = '0 4px 16px rgba(67,97,238,0.12)';
257
- el.style.transform = 'translateY(-3px) scale(1.03)';
258
- }}
259
- onMouseLeave={(e) => {
260
- const el = e.currentTarget;
261
- el.style.borderColor = '#eaeaea';
262
- el.style.boxShadow = 'none';
263
- el.style.transform = 'translateY(0) scale(1)';
264
- }}
265
- >
266
- <ComponentThumbnail componentType={type} width={140} />
267
- </div>
268
- <span
269
- style={{
270
- fontSize: 11,
271
- color: '#888',
272
- fontWeight: 500,
273
- letterSpacing: '0.3px',
274
- textAlign: 'center',
275
- pointerEvents: 'none',
276
- }}
277
- >
278
- {getComponentLabel(type, t)}
279
- </span>
280
- </div>
281
- );
282
- }
283
-
284
- // ── 多 variant:保持 Popover hover 展开 ──
285
- const popoverContent = (
286
- <div
287
- style={{
288
- display: 'flex',
289
- alignItems: 'center',
290
- gap: 16,
291
- padding: 4,
292
- }}
293
- >
294
- {variants.map((v) => (
295
- <div
296
- key={v.name}
297
- draggable
298
- onDragStart={(e) => handleVariantDragStart(e, type, v.code)}
299
- style={{
300
- display: 'flex',
301
- flexDirection: 'column',
302
- alignItems: 'center',
303
- gap: 6,
304
- cursor: 'grab',
305
- userSelect: 'none',
306
- flexShrink: 0,
307
- }}
308
- >
309
- <div
310
- style={{
311
- border: '1px solid #e8e8e8',
312
- borderRadius: 8,
313
- overflow: 'hidden',
314
- background: '#fff',
315
- transition: 'all 180ms ease',
316
- }}
317
- onMouseEnter={(e) => {
318
- const el = e.currentTarget;
319
- el.style.borderColor = '#4361ee';
320
- el.style.boxShadow = '0 4px 12px rgba(67,97,238,0.15)';
321
- el.style.transform = 'translateY(-3px) scale(1.02)';
322
- }}
323
- onMouseLeave={(e) => {
324
- const el = e.currentTarget;
325
- el.style.borderColor = '#e8e8e8';
326
- el.style.boxShadow = 'none';
327
- el.style.transform = 'translateY(0) scale(1)';
328
- }}
329
- >
330
- <ComponentThumbnail
331
- componentType={type}
332
- code={v.code}
333
- width={124}
334
- />
335
- </div>
336
- <span
337
- style={{
338
- fontSize: 10,
339
- color: '#666',
340
- fontWeight: 500,
341
- textAlign: 'center',
342
- pointerEvents: 'none',
343
- }}
344
- >
345
- {getVariantLabel(v.label, t)}
346
- </span>
347
- </div>
348
- ))}
349
- </div>
350
- );
351
-
352
- return (
353
- <Popover
354
- key={type}
355
- content={popoverContent}
356
- title={
357
- <span
358
- style={{ fontSize: 12, fontWeight: 600, color: '#333' }}
359
- >
360
- {getComponentLabel(type, t)}
361
- </span>
362
- }
363
- trigger="hover"
364
- placement="bottomLeft"
365
- mouseEnterDelay={0.2}
366
- mouseLeaveDelay={0.3}
367
- >
368
- <div
369
- style={{
370
- display: 'flex',
371
- flexDirection: 'column',
372
- alignItems: 'center',
373
- gap: 8,
374
- cursor: 'grab',
375
- userSelect: 'none',
376
- flexShrink: 0,
377
- width: 140,
378
- }}
379
- >
380
- <div
381
- style={{
382
- border: '1px solid #eaeaea',
383
- borderRadius: 10,
384
- overflow: 'hidden',
385
- background: '#fff',
386
- transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1)',
387
- }}
388
- onMouseEnter={(e) => {
389
- const el = e.currentTarget;
390
- el.style.borderColor = '#4361ee';
391
- el.style.boxShadow = '0 4px 16px rgba(67,97,238,0.12)';
392
- el.style.transform = 'translateY(-3px) scale(1.03)';
393
- }}
394
- onMouseLeave={(e) => {
395
- const el = e.currentTarget;
396
- el.style.borderColor = '#eaeaea';
397
- el.style.boxShadow = 'none';
398
- el.style.transform = 'translateY(0) scale(1)';
399
- }}
400
- >
401
- <ComponentThumbnail componentType={type} width={140} />
402
- </div>
403
- <span
404
- style={{
405
- fontSize: 11,
406
- color: '#888',
407
- fontWeight: 500,
408
- letterSpacing: '0.3px',
409
- textAlign: 'center',
410
- pointerEvents: 'none',
411
- }}
412
- >
413
- {getComponentLabel(type, t)}
414
- </span>
415
- </div>
416
- </Popover>
417
- );
418
- })}
419
- </div>
420
- )}
421
185
  </div>
422
186
  );
423
187
  };
@@ -7,6 +7,7 @@ import {
7
7
  SwapOutlined,
8
8
  DownloadOutlined,
9
9
  SettingOutlined,
10
+ ColumnHeightOutlined,
10
11
  } from '@ant-design/icons';
11
12
  import {
12
13
  readTableFlag,
@@ -597,8 +598,8 @@ function barChartActions(
597
598
  : []),
598
599
  {
599
600
  key: 'bar-direction',
600
- icon: <PlusOutlined />,
601
- label: t?.('canvas.menu.direction') ?? '方向',
601
+ icon: <ColumnHeightOutlined />,
602
+ label: t?.('canvas.menu.direction') ?? '图表方向',
602
603
  children: [
603
604
  {
604
605
  key: 'direction-vertical',
@@ -7,6 +7,8 @@ import {
7
7
  SettingOutlined,
8
8
  DatabaseOutlined,
9
9
  DeleteOutlined,
10
+ ToolOutlined,
11
+ EyeOutlined,
10
12
  } from '@ant-design/icons';
11
13
  import { CANVAS_CONFIG_REGISTRY } from '../canvasConfigRegistry';
12
14
  import { CANVAS_CONTEXT_MENU_REGISTRY } from '../canvasContextMenuRegistry';
@@ -38,6 +40,51 @@ interface ItemMenuState {
38
40
  id: string;
39
41
  }
40
42
 
43
+ /** 显隐切换类菜单项的 key —— 这些会被收纳到「显示元素」子菜单 */
44
+ const VISIBILITY_TOGGLE_KEYS = new Set([
45
+ 'toggle-grid',
46
+ 'toggle-label',
47
+ 'toggle-legend',
48
+ 'toggle-axis',
49
+ 'toggle-dots',
50
+ 'toggle-dots-label',
51
+ 'toggle-download',
52
+ ]);
53
+
54
+ /** 把 specificActions 里的显隐切换项收纳到一个子菜单下 */
55
+ function regroupVisibilityToggles(
56
+ items: MenuProps['items'],
57
+ t: TFunction,
58
+ ): MenuProps['items'] {
59
+ if (!items || items.length === 0) return items;
60
+ const visibility: NonNullable<MenuProps['items']> = [];
61
+ const rest: NonNullable<MenuProps['items']> = [];
62
+ for (const item of items) {
63
+ if (
64
+ item &&
65
+ typeof item === 'object' &&
66
+ 'key' in item &&
67
+ typeof item.key === 'string' &&
68
+ VISIBILITY_TOGGLE_KEYS.has(item.key)
69
+ ) {
70
+ // 子菜单里去掉 danger 红色,避免视觉过于扎眼
71
+ const { danger, icon: _icon, ...itemRest } = item as any;
72
+ visibility.push(itemRest);
73
+ } else {
74
+ rest.push(item);
75
+ }
76
+ }
77
+ // 少于 2 个不值得收
78
+ if (visibility.length < 2) return items;
79
+ rest.push({
80
+ key: 'visibility-group',
81
+ icon: <EyeOutlined />,
82
+ label: t('canvas.menu.visibilityGroup', { defaultValue: '显示元素' }),
83
+ children: visibility,
84
+ });
85
+ return rest;
86
+ }
87
+
41
88
  /** 画布组件右键菜单:开关 state + 三层菜单项(组件特有 / 通用 / 代码级)的组装逻辑 */
42
89
  export function useCanvasContextMenu({
43
90
  items,
@@ -71,7 +118,7 @@ export function useCanvasContextMenu({
71
118
  // ── 第 1 层:组件特有操作 ──
72
119
  const specificActionsFactory =
73
120
  CANVAS_CONTEXT_MENU_REGISTRY[componentType];
74
- const specificActions = specificActionsFactory
121
+ const rawSpecificActions = specificActionsFactory
75
122
  ? specificActionsFactory(
76
123
  item?.code ?? '',
77
124
  (newCode) => {
@@ -82,6 +129,8 @@ export function useCanvasContextMenu({
82
129
  t,
83
130
  )
84
131
  : [];
132
+ // 把 grid/label/legend/axis/dots/download 等显隐项收纳到子菜单
133
+ const specificActions = regroupVisibilityToggles(rawSpecificActions, t);
85
134
 
86
135
  // ── 第 2 层:通用操作(检查元素) ──
87
136
  const commonActions: MenuProps['items'] = [
@@ -197,16 +246,19 @@ export function useCanvasContextMenu({
197
246
  ];
198
247
 
199
248
  // ── 第 3 层:代码级操作 ──
200
- const codeActions: MenuProps['items'] = [
249
+ const advancedActions: MenuProps['items'] = [
201
250
  {
202
251
  key: 'edit',
203
- icon: <EditOutlined />,
204
- label: t('canvas.editCode'),
252
+ icon: <ToolOutlined />,
253
+ label: t('canvas.adjustCode', { defaultValue: '调整代码' }),
205
254
  onClick: () => {
206
255
  openEditCode(itemMenu.id);
207
256
  setItemMenu(null);
208
257
  },
209
258
  },
259
+ ];
260
+
261
+ const opActions: MenuProps['items'] = [
210
262
  {
211
263
  key: 'copy',
212
264
  icon: <CopyOutlined />,
@@ -228,15 +280,34 @@ export function useCanvasContextMenu({
228
280
  },
229
281
  ];
230
282
 
231
- // ── 组装三层 ──
283
+ // ── 组装:分组标题 + 分割线 ──
232
284
  const result: MenuProps['items'] = [];
233
285
  if (specificActions && specificActions.length > 0) {
234
- result.push(...specificActions);
286
+ result.push({
287
+ key: 'g-component',
288
+ type: 'group',
289
+ label: t('canvas.menuGroups.component', {
290
+ defaultValue: '组件设置',
291
+ }),
292
+ children: specificActions as any,
293
+ });
235
294
  result.push({ type: 'divider' as const });
236
295
  }
237
- result.push(...commonActions);
296
+ result.push({
297
+ key: 'g-data',
298
+ type: 'group',
299
+ label: t('canvas.menuGroups.data', { defaultValue: '数据与样式' }),
300
+ children: commonActions as any,
301
+ });
302
+ result.push({ type: 'divider' as const });
303
+ result.push({
304
+ key: 'g-advanced',
305
+ type: 'group',
306
+ label: t('canvas.menuGroups.advanced', { defaultValue: '高级' }),
307
+ children: advancedActions as any,
308
+ });
238
309
  result.push({ type: 'divider' as const });
239
- result.push(...codeActions);
310
+ result.push(...opActions);
240
311
 
241
312
  return result;
242
313
  })()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadmin2n/schematics",
3
- "version": "0.0.111",
3
+ "version": "0.0.113",
4
4
  "description": "Gadmin - modern, fast, powerful node.js web framework (@schematics)",
5
5
  "main": "dist/index.js",
6
6
  "files": [