@gadmin2n/schematics 0.0.109 → 0.0.110
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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +20 -203
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/LivePreview.tsx +11 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasContextMenuRegistry.tsx +15 -16
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasContextMenu.tsx +246 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/utils/jsLiteralParser.ts +50 -0
- package/package.json +1 -1
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx
CHANGED
|
@@ -8,15 +8,9 @@ import React, {
|
|
|
8
8
|
import { GridLayout, useContainerWidth } from 'react-grid-layout';
|
|
9
9
|
import type { LayoutItem } from 'react-grid-layout';
|
|
10
10
|
import { Dropdown, Modal, Checkbox, Input, Button, Space } from 'antd';
|
|
11
|
-
import type { MenuProps } from 'antd';
|
|
12
11
|
import {
|
|
13
12
|
PlusOutlined,
|
|
14
|
-
DeleteOutlined,
|
|
15
|
-
EditOutlined,
|
|
16
|
-
CopyOutlined,
|
|
17
|
-
SettingOutlined,
|
|
18
13
|
PlusCircleOutlined,
|
|
19
|
-
DatabaseOutlined,
|
|
20
14
|
MinusCircleOutlined,
|
|
21
15
|
} from '@ant-design/icons';
|
|
22
16
|
import CanvasCell from './CanvasCell';
|
|
@@ -26,9 +20,9 @@ import { CANVAS_COMPONENTS, CANVAS_DEFAULTS } from './canvasDefaults';
|
|
|
26
20
|
import { CANVAS_CONFIG_REGISTRY } from './canvasConfigRegistry';
|
|
27
21
|
import { useAgent } from '@/components/agentPanel/AgentContext';
|
|
28
22
|
import { generatePrompt } from '@/components/agentPanel/promptGenerator';
|
|
29
|
-
import { CANVAS_CONTEXT_MENU_REGISTRY } from './canvasContextMenuRegistry';
|
|
30
23
|
import type { MenuActionContext } from './canvasContextMenuRegistry';
|
|
31
24
|
import type { CanvasConfigModalProps } from './canvasConfigRegistry';
|
|
25
|
+
import { useCanvasContextMenu } from './hooks/useCanvasContextMenu';
|
|
32
26
|
import { useIsAgentAllowed } from 'config/agentAllowed';
|
|
33
27
|
import NumCardDataSourceModal from './components/NumCardDataSourceModal';
|
|
34
28
|
import TableDataSourceModal from './components/TableDataSourceModal';
|
|
@@ -387,7 +381,7 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
|
|
|
387
381
|
}) => {
|
|
388
382
|
const { items, layout: currentLayout, onBothChange } = liveRef.current;
|
|
389
383
|
onBothChange(
|
|
390
|
-
[...items, item],
|
|
384
|
+
[...items, { ...item, code: syncCodeHeight(item.code, layout.h) }],
|
|
391
385
|
[
|
|
392
386
|
...currentLayout,
|
|
393
387
|
{
|
|
@@ -548,203 +542,26 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
|
|
|
548
542
|
|
|
549
543
|
// ── 右键菜单 ──
|
|
550
544
|
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const item = items.find((i) => i.id === itemMenu.id);
|
|
567
|
-
const componentType = item?.componentType ?? '';
|
|
568
|
-
const hasConfig = componentType in CANVAS_CONFIG_REGISTRY;
|
|
569
|
-
|
|
570
|
-
// ── 第 1 层:组件特有操作 ──
|
|
571
|
-
const specificActionsFactory =
|
|
572
|
-
CANVAS_CONTEXT_MENU_REGISTRY[componentType];
|
|
573
|
-
const specificActions = specificActionsFactory
|
|
574
|
-
? specificActionsFactory(
|
|
575
|
-
item?.code ?? '',
|
|
576
|
-
(newCode) => {
|
|
577
|
-
updateCode(itemMenu.id, newCode);
|
|
578
|
-
},
|
|
579
|
-
() => setItemMenu(null),
|
|
580
|
-
menuActionContext,
|
|
581
|
-
t,
|
|
582
|
-
)
|
|
583
|
-
: [];
|
|
584
|
-
|
|
585
|
-
// ── 第 2 层:通用操作(检查元素) ──
|
|
586
|
-
const commonActions: MenuProps['items'] = [
|
|
587
|
-
...(hasConfig
|
|
588
|
-
? [
|
|
589
|
-
{
|
|
590
|
-
key: 'config',
|
|
591
|
-
icon: <SettingOutlined />,
|
|
592
|
-
label:
|
|
593
|
-
componentType === 'MultiChart'
|
|
594
|
-
? (t('canvas.configCharts') ?? '配置显示图表与顺序')
|
|
595
|
-
: componentType === 'Table'
|
|
596
|
-
? (t('canvas.menu.configColumns') ?? '配置列')
|
|
597
|
-
: t('canvas.config'),
|
|
598
|
-
onClick: () => {
|
|
599
|
-
setConfigModal({ id: itemMenu.id, componentType });
|
|
600
|
-
setItemMenu(null);
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
]
|
|
604
|
-
: []),
|
|
605
|
-
{
|
|
606
|
-
key: 'data-source',
|
|
607
|
-
icon: <DatabaseOutlined />,
|
|
608
|
-
label: t('canvas.dataSource.label'),
|
|
609
|
-
onClick: () => {
|
|
610
|
-
if (
|
|
611
|
-
componentType === 'NumCard' ||
|
|
612
|
-
componentType === 'Table' ||
|
|
613
|
-
componentType === 'BarChart' ||
|
|
614
|
-
componentType === 'LineChart' ||
|
|
615
|
-
componentType === 'RadarChart' ||
|
|
616
|
-
componentType === 'MultiChart'
|
|
617
|
-
) {
|
|
618
|
-
setDataSourceModal({
|
|
619
|
-
id: itemMenu.id,
|
|
620
|
-
componentType,
|
|
621
|
-
code: item?.code ?? '',
|
|
622
|
-
});
|
|
623
|
-
} else {
|
|
624
|
-
menuActionContext.showPrompt({
|
|
625
|
-
label: t('canvas.dataSource.label'),
|
|
626
|
-
placeholder: t('canvas.dataSource.placeholder'),
|
|
627
|
-
defaultValue: '',
|
|
628
|
-
onConfirm: (value) => {
|
|
629
|
-
if (!value.trim()) return;
|
|
630
|
-
const prompt = generatePrompt({
|
|
631
|
-
skill: 'canvas-sql-query',
|
|
632
|
-
pageInfo: agent?.pageInfo ?? {
|
|
633
|
-
resourceName: '',
|
|
634
|
-
pageType: 'unknown',
|
|
635
|
-
path: '',
|
|
636
|
-
sourceFilePath: null,
|
|
637
|
-
},
|
|
638
|
-
userPrompt: `为此 ${componentType} 组件用 SQL 查询真实数据并填充:${value.trim()}`,
|
|
639
|
-
inspectedElement: {
|
|
640
|
-
type: 'canvas-component' as any,
|
|
641
|
-
resource: 'canvas',
|
|
642
|
-
x: 0,
|
|
643
|
-
y: 0,
|
|
644
|
-
meta: {
|
|
645
|
-
componentType,
|
|
646
|
-
canvasItemId: itemMenu.id,
|
|
647
|
-
code: item?.code ?? '',
|
|
648
|
-
},
|
|
649
|
-
},
|
|
650
|
-
});
|
|
651
|
-
agent?.sendPrompt(prompt);
|
|
652
|
-
},
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
setItemMenu(null);
|
|
656
|
-
},
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
key: 'custom-agent',
|
|
660
|
-
icon: <EditOutlined />,
|
|
661
|
-
label: t('canvas.custom'),
|
|
662
|
-
onClick: () => {
|
|
663
|
-
setItemMenu(null);
|
|
664
|
-
menuActionContext.showPrompt({
|
|
665
|
-
label: t('canvas.customAction'),
|
|
666
|
-
placeholder: t('canvas.customPlaceholder'),
|
|
667
|
-
defaultValue: '',
|
|
668
|
-
onConfirm: (value) => {
|
|
669
|
-
if (!value.trim()) return;
|
|
670
|
-
const prompt = generatePrompt({
|
|
671
|
-
skill: 'canvas-component-edit',
|
|
672
|
-
pageInfo: agent?.pageInfo ?? {
|
|
673
|
-
resourceName: '',
|
|
674
|
-
pageType: 'unknown',
|
|
675
|
-
path: '',
|
|
676
|
-
sourceFilePath: null,
|
|
677
|
-
},
|
|
678
|
-
userPrompt: value.trim(),
|
|
679
|
-
inspectedElement: {
|
|
680
|
-
type: 'canvas-component' as any,
|
|
681
|
-
resource: 'canvas',
|
|
682
|
-
x: 0,
|
|
683
|
-
y: 0,
|
|
684
|
-
meta: {
|
|
685
|
-
componentType,
|
|
686
|
-
canvasItemId: itemMenu.id,
|
|
687
|
-
code: item?.code ?? '',
|
|
688
|
-
},
|
|
689
|
-
},
|
|
690
|
-
});
|
|
691
|
-
agent?.sendPrompt(prompt);
|
|
692
|
-
},
|
|
693
|
-
});
|
|
694
|
-
},
|
|
695
|
-
},
|
|
696
|
-
];
|
|
697
|
-
|
|
698
|
-
// ── 第 3 层:代码级操作 ──
|
|
699
|
-
const codeActions: MenuProps['items'] = [
|
|
700
|
-
{
|
|
701
|
-
key: 'edit',
|
|
702
|
-
icon: <EditOutlined />,
|
|
703
|
-
label: t('canvas.editCode'),
|
|
704
|
-
onClick: () => {
|
|
705
|
-
setEditingId(itemMenu.id);
|
|
706
|
-
setItemMenu(null);
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
key: 'copy',
|
|
711
|
-
icon: <CopyOutlined />,
|
|
712
|
-
label: t('canvas.duplicate'),
|
|
713
|
-
onClick: () => {
|
|
714
|
-
duplicateComponent(itemMenu.id);
|
|
715
|
-
setItemMenu(null);
|
|
716
|
-
},
|
|
717
|
-
},
|
|
718
|
-
{
|
|
719
|
-
key: 'delete',
|
|
720
|
-
icon: <DeleteOutlined />,
|
|
721
|
-
label: t('canvas.delete'),
|
|
722
|
-
danger: true,
|
|
723
|
-
onClick: () => {
|
|
724
|
-
removeComponent(itemMenu.id);
|
|
725
|
-
setItemMenu(null);
|
|
726
|
-
},
|
|
727
|
-
},
|
|
728
|
-
];
|
|
729
|
-
|
|
730
|
-
// ── 组装三层 ──
|
|
731
|
-
const result: MenuProps['items'] = [];
|
|
732
|
-
if (specificActions && specificActions.length > 0) {
|
|
733
|
-
result.push(...specificActions);
|
|
734
|
-
result.push({ type: 'divider' as const });
|
|
735
|
-
}
|
|
736
|
-
result.push(...commonActions);
|
|
737
|
-
result.push({ type: 'divider' as const });
|
|
738
|
-
result.push(...codeActions);
|
|
739
|
-
|
|
740
|
-
return result;
|
|
741
|
-
})()
|
|
742
|
-
: [];
|
|
545
|
+
const { itemMenu, handleItemContextMenu, closeItemMenu, itemMenuItems } =
|
|
546
|
+
useCanvasContextMenu({
|
|
547
|
+
items,
|
|
548
|
+
updateCode,
|
|
549
|
+
duplicateComponent,
|
|
550
|
+
removeComponent,
|
|
551
|
+
menuActionContext,
|
|
552
|
+
t,
|
|
553
|
+
agent,
|
|
554
|
+
openConfigModal: (id, componentType) =>
|
|
555
|
+
setConfigModal({ id, componentType }),
|
|
556
|
+
openDataSourceModal: (id, componentType, code) =>
|
|
557
|
+
setDataSourceModal({ id, componentType, code }),
|
|
558
|
+
openEditCode: (id) => setEditingId(id),
|
|
559
|
+
});
|
|
743
560
|
|
|
744
561
|
const handleCanvasClick = useCallback(() => {
|
|
745
562
|
setSelectedId(null);
|
|
746
|
-
|
|
747
|
-
}, []);
|
|
563
|
+
closeItemMenu();
|
|
564
|
+
}, [closeItemMenu]);
|
|
748
565
|
|
|
749
566
|
const editingItem = editingId ? items.find((i) => i.id === editingId) : null;
|
|
750
567
|
const editingDef = editingItem
|
|
@@ -938,7 +755,7 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
|
|
|
938
755
|
open
|
|
939
756
|
trigger={['click']}
|
|
940
757
|
onOpenChange={(open) => {
|
|
941
|
-
if (!open)
|
|
758
|
+
if (!open) closeItemMenu();
|
|
942
759
|
}}
|
|
943
760
|
>
|
|
944
761
|
<div
|
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/LivePreview.tsx
CHANGED
|
@@ -27,7 +27,16 @@ import {
|
|
|
27
27
|
Section,
|
|
28
28
|
} from '@gadmin2n/charts';
|
|
29
29
|
import * as echarts from 'echarts';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
Form,
|
|
32
|
+
Select,
|
|
33
|
+
Input,
|
|
34
|
+
DatePicker,
|
|
35
|
+
Tag,
|
|
36
|
+
Button,
|
|
37
|
+
Space,
|
|
38
|
+
Spin,
|
|
39
|
+
} from 'antd';
|
|
31
40
|
import {
|
|
32
41
|
CaretUpOutlined,
|
|
33
42
|
CaretDownOutlined,
|
|
@@ -59,6 +68,7 @@ const SCOPE: Record<string, unknown> = {
|
|
|
59
68
|
Tag,
|
|
60
69
|
Button,
|
|
61
70
|
Space,
|
|
71
|
+
Spin,
|
|
62
72
|
CaretUpOutlined,
|
|
63
73
|
CaretDownOutlined,
|
|
64
74
|
UserOutlined,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
writeMultiChartTableFlags,
|
|
17
17
|
buildVirtualTableCode,
|
|
18
18
|
} from './utils/tableCodeUtils';
|
|
19
|
+
import { parseJsLiteralProp } from './utils/jsLiteralParser';
|
|
19
20
|
|
|
20
21
|
// ─── 组件特有右键菜单操作注册表 ────────────────────────────────────────────────
|
|
21
22
|
//
|
|
@@ -1068,25 +1069,16 @@ function parseDefaultVariant(code: string): string {
|
|
|
1068
1069
|
|
|
1069
1070
|
/** 从 code 中解析 charts 列表 */
|
|
1070
1071
|
function parseChartsList(code: string): string[] {
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
try {
|
|
1074
|
-
return JSON.parse(match[1].replace(/'/g, '"'));
|
|
1075
|
-
} catch {
|
|
1076
|
-
return ['bar', 'line', 'pie'];
|
|
1077
|
-
}
|
|
1072
|
+
const parsed = parseJsLiteralProp(code, 'charts');
|
|
1073
|
+
return Array.isArray(parsed) ? parsed : ['bar', 'line', 'pie'];
|
|
1078
1074
|
}
|
|
1079
1075
|
|
|
1080
|
-
/** 从 code 中解析 chartConfig
|
|
1076
|
+
/** 从 code 中解析 chartConfig */
|
|
1081
1077
|
function parseChartConfig(code: string): Record<string, Record<string, any>> {
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
return JSON.parse(match[1]);
|
|
1087
|
-
} catch {
|
|
1088
|
-
return {};
|
|
1089
|
-
}
|
|
1078
|
+
const parsed = parseJsLiteralProp(code, 'chartConfig');
|
|
1079
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1080
|
+
? (parsed as Record<string, Record<string, any>>)
|
|
1081
|
+
: {};
|
|
1090
1082
|
}
|
|
1091
1083
|
|
|
1092
1084
|
/** 将 chartConfig 写回 code 字符串 */
|
|
@@ -1186,6 +1178,8 @@ function multiChartActions(
|
|
|
1186
1178
|
virtualCode += ` ${k}={${v}}\n`;
|
|
1187
1179
|
} else if (typeof v === 'string') {
|
|
1188
1180
|
virtualCode += ` ${k}="${v}"\n`;
|
|
1181
|
+
} else if (typeof v === 'number') {
|
|
1182
|
+
virtualCode += ` ${k}={${v}}\n`;
|
|
1189
1183
|
}
|
|
1190
1184
|
}
|
|
1191
1185
|
virtualCode += ` testId="virtual"\n/>`;
|
|
@@ -1205,6 +1199,11 @@ function multiChartActions(
|
|
|
1205
1199
|
for (const m of strMatches) {
|
|
1206
1200
|
if (m[1] !== 'testId') newConfig[m[1]] = m[2];
|
|
1207
1201
|
}
|
|
1202
|
+
// 解析 number props: xxx={123}
|
|
1203
|
+
const numMatches = newVirtualCode.matchAll(/(\w+)\s*=\s*\{(\d+)\}/g);
|
|
1204
|
+
for (const m of numMatches) {
|
|
1205
|
+
if (m[1] !== 'testId') newConfig[m[1]] = Number(m[2]);
|
|
1206
|
+
}
|
|
1208
1207
|
|
|
1209
1208
|
// 更新 chartConfig
|
|
1210
1209
|
const updatedConfig = { ...chartConfig, [activeChart]: newConfig };
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import type { MenuProps } from 'antd';
|
|
3
|
+
import type { TFunction } from 'i18next';
|
|
4
|
+
import {
|
|
5
|
+
EditOutlined,
|
|
6
|
+
CopyOutlined,
|
|
7
|
+
SettingOutlined,
|
|
8
|
+
DatabaseOutlined,
|
|
9
|
+
DeleteOutlined,
|
|
10
|
+
} from '@ant-design/icons';
|
|
11
|
+
import { CANVAS_CONFIG_REGISTRY } from '../canvasConfigRegistry';
|
|
12
|
+
import { CANVAS_CONTEXT_MENU_REGISTRY } from '../canvasContextMenuRegistry';
|
|
13
|
+
import type { MenuActionContext } from '../canvasContextMenuRegistry';
|
|
14
|
+
import { generatePrompt } from '@/components/agentPanel/promptGenerator';
|
|
15
|
+
import type { useAgent } from '@/components/agentPanel/AgentContext';
|
|
16
|
+
import type { CanvasItem as CanvasItemType } from '../types';
|
|
17
|
+
|
|
18
|
+
interface UseCanvasContextMenuParams {
|
|
19
|
+
items: CanvasItemType[];
|
|
20
|
+
updateCode: (id: string, code: string) => void;
|
|
21
|
+
duplicateComponent: (id: string) => void;
|
|
22
|
+
removeComponent: (id: string) => void;
|
|
23
|
+
menuActionContext: MenuActionContext;
|
|
24
|
+
t: TFunction;
|
|
25
|
+
agent: ReturnType<typeof useAgent>;
|
|
26
|
+
openConfigModal: (id: string, componentType: string) => void;
|
|
27
|
+
openDataSourceModal: (
|
|
28
|
+
id: string,
|
|
29
|
+
componentType: string,
|
|
30
|
+
code: string,
|
|
31
|
+
) => void;
|
|
32
|
+
openEditCode: (id: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ItemMenuState {
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
id: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 画布组件右键菜单:开关 state + 三层菜单项(组件特有 / 通用 / 代码级)的组装逻辑 */
|
|
42
|
+
export function useCanvasContextMenu({
|
|
43
|
+
items,
|
|
44
|
+
updateCode,
|
|
45
|
+
duplicateComponent,
|
|
46
|
+
removeComponent,
|
|
47
|
+
menuActionContext,
|
|
48
|
+
t,
|
|
49
|
+
agent,
|
|
50
|
+
openConfigModal,
|
|
51
|
+
openDataSourceModal,
|
|
52
|
+
openEditCode,
|
|
53
|
+
}: UseCanvasContextMenuParams) {
|
|
54
|
+
const [itemMenu, setItemMenu] = useState<ItemMenuState | null>(null);
|
|
55
|
+
|
|
56
|
+
const handleItemContextMenu = useCallback(
|
|
57
|
+
(e: React.MouseEvent, id: string) => {
|
|
58
|
+
setItemMenu({ x: e.clientX, y: e.clientY, id });
|
|
59
|
+
},
|
|
60
|
+
[],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const closeItemMenu = useCallback(() => setItemMenu(null), []);
|
|
64
|
+
|
|
65
|
+
const itemMenuItems: MenuProps['items'] = itemMenu
|
|
66
|
+
? (() => {
|
|
67
|
+
const item = items.find((i) => i.id === itemMenu.id);
|
|
68
|
+
const componentType = item?.componentType ?? '';
|
|
69
|
+
const hasConfig = componentType in CANVAS_CONFIG_REGISTRY;
|
|
70
|
+
|
|
71
|
+
// ── 第 1 层:组件特有操作 ──
|
|
72
|
+
const specificActionsFactory =
|
|
73
|
+
CANVAS_CONTEXT_MENU_REGISTRY[componentType];
|
|
74
|
+
const specificActions = specificActionsFactory
|
|
75
|
+
? specificActionsFactory(
|
|
76
|
+
item?.code ?? '',
|
|
77
|
+
(newCode) => {
|
|
78
|
+
updateCode(itemMenu.id, newCode);
|
|
79
|
+
},
|
|
80
|
+
() => setItemMenu(null),
|
|
81
|
+
menuActionContext,
|
|
82
|
+
t,
|
|
83
|
+
)
|
|
84
|
+
: [];
|
|
85
|
+
|
|
86
|
+
// ── 第 2 层:通用操作(检查元素) ──
|
|
87
|
+
const commonActions: MenuProps['items'] = [
|
|
88
|
+
...(hasConfig
|
|
89
|
+
? [
|
|
90
|
+
{
|
|
91
|
+
key: 'config',
|
|
92
|
+
icon: <SettingOutlined />,
|
|
93
|
+
label:
|
|
94
|
+
componentType === 'MultiChart'
|
|
95
|
+
? (t('canvas.configCharts') ?? '配置显示图表与顺序')
|
|
96
|
+
: componentType === 'Table'
|
|
97
|
+
? (t('canvas.menu.configColumns') ?? '配置列')
|
|
98
|
+
: t('canvas.config'),
|
|
99
|
+
onClick: () => {
|
|
100
|
+
openConfigModal(itemMenu.id, componentType);
|
|
101
|
+
setItemMenu(null);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
]
|
|
105
|
+
: []),
|
|
106
|
+
{
|
|
107
|
+
key: 'data-source',
|
|
108
|
+
icon: <DatabaseOutlined />,
|
|
109
|
+
label: t('canvas.dataSource.label'),
|
|
110
|
+
onClick: () => {
|
|
111
|
+
if (
|
|
112
|
+
componentType === 'NumCard' ||
|
|
113
|
+
componentType === 'Table' ||
|
|
114
|
+
componentType === 'BarChart' ||
|
|
115
|
+
componentType === 'LineChart' ||
|
|
116
|
+
componentType === 'RadarChart' ||
|
|
117
|
+
componentType === 'MultiChart'
|
|
118
|
+
) {
|
|
119
|
+
openDataSourceModal(
|
|
120
|
+
itemMenu.id,
|
|
121
|
+
componentType,
|
|
122
|
+
item?.code ?? '',
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
menuActionContext.showPrompt({
|
|
126
|
+
label: t('canvas.dataSource.label'),
|
|
127
|
+
placeholder: t('canvas.dataSource.placeholder'),
|
|
128
|
+
defaultValue: '',
|
|
129
|
+
onConfirm: (value) => {
|
|
130
|
+
if (!value.trim()) return;
|
|
131
|
+
const prompt = generatePrompt({
|
|
132
|
+
skill: 'canvas-sql-query',
|
|
133
|
+
pageInfo: agent?.pageInfo ?? {
|
|
134
|
+
resourceName: '',
|
|
135
|
+
pageType: 'unknown',
|
|
136
|
+
path: '',
|
|
137
|
+
sourceFilePath: null,
|
|
138
|
+
},
|
|
139
|
+
userPrompt: `为此 ${componentType} 组件用 SQL 查询真实数据并填充:${value.trim()}`,
|
|
140
|
+
inspectedElement: {
|
|
141
|
+
type: 'canvas-component' as any,
|
|
142
|
+
resource: 'canvas',
|
|
143
|
+
x: 0,
|
|
144
|
+
y: 0,
|
|
145
|
+
meta: {
|
|
146
|
+
componentType,
|
|
147
|
+
canvasItemId: itemMenu.id,
|
|
148
|
+
code: item?.code ?? '',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
agent?.sendPrompt(prompt);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
setItemMenu(null);
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: 'custom-agent',
|
|
161
|
+
icon: <EditOutlined />,
|
|
162
|
+
label: t('canvas.custom'),
|
|
163
|
+
onClick: () => {
|
|
164
|
+
setItemMenu(null);
|
|
165
|
+
menuActionContext.showPrompt({
|
|
166
|
+
label: t('canvas.customAction'),
|
|
167
|
+
placeholder: t('canvas.customPlaceholder'),
|
|
168
|
+
defaultValue: '',
|
|
169
|
+
onConfirm: (value) => {
|
|
170
|
+
if (!value.trim()) return;
|
|
171
|
+
const prompt = generatePrompt({
|
|
172
|
+
skill: 'canvas-component-edit',
|
|
173
|
+
pageInfo: agent?.pageInfo ?? {
|
|
174
|
+
resourceName: '',
|
|
175
|
+
pageType: 'unknown',
|
|
176
|
+
path: '',
|
|
177
|
+
sourceFilePath: null,
|
|
178
|
+
},
|
|
179
|
+
userPrompt: value.trim(),
|
|
180
|
+
inspectedElement: {
|
|
181
|
+
type: 'canvas-component' as any,
|
|
182
|
+
resource: 'canvas',
|
|
183
|
+
x: 0,
|
|
184
|
+
y: 0,
|
|
185
|
+
meta: {
|
|
186
|
+
componentType,
|
|
187
|
+
canvasItemId: itemMenu.id,
|
|
188
|
+
code: item?.code ?? '',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
agent?.sendPrompt(prompt);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// ── 第 3 层:代码级操作 ──
|
|
200
|
+
const codeActions: MenuProps['items'] = [
|
|
201
|
+
{
|
|
202
|
+
key: 'edit',
|
|
203
|
+
icon: <EditOutlined />,
|
|
204
|
+
label: t('canvas.editCode'),
|
|
205
|
+
onClick: () => {
|
|
206
|
+
openEditCode(itemMenu.id);
|
|
207
|
+
setItemMenu(null);
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
key: 'copy',
|
|
212
|
+
icon: <CopyOutlined />,
|
|
213
|
+
label: t('canvas.duplicate'),
|
|
214
|
+
onClick: () => {
|
|
215
|
+
duplicateComponent(itemMenu.id);
|
|
216
|
+
setItemMenu(null);
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
key: 'delete',
|
|
221
|
+
icon: <DeleteOutlined />,
|
|
222
|
+
label: t('canvas.delete'),
|
|
223
|
+
danger: true,
|
|
224
|
+
onClick: () => {
|
|
225
|
+
removeComponent(itemMenu.id);
|
|
226
|
+
setItemMenu(null);
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
// ── 组装三层 ──
|
|
232
|
+
const result: MenuProps['items'] = [];
|
|
233
|
+
if (specificActions && specificActions.length > 0) {
|
|
234
|
+
result.push(...specificActions);
|
|
235
|
+
result.push({ type: 'divider' as const });
|
|
236
|
+
}
|
|
237
|
+
result.push(...commonActions);
|
|
238
|
+
result.push({ type: 'divider' as const });
|
|
239
|
+
result.push(...codeActions);
|
|
240
|
+
|
|
241
|
+
return result;
|
|
242
|
+
})()
|
|
243
|
+
: [];
|
|
244
|
+
|
|
245
|
+
return { itemMenu, handleItemContextMenu, closeItemMenu, itemMenuItems };
|
|
246
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从 JSX 代码中提取某个 prop 的字面量值,按 JS 表达式语法求值
|
|
3
|
+
* (兼容裸 key、单引号字符串等非严格 JSON 写法,因为这本来就是 JS/JSX,不是 JSON)。
|
|
4
|
+
* 求值失败(语法错误、未找到该 prop 等)时返回 undefined。
|
|
5
|
+
*/
|
|
6
|
+
export function parseJsLiteralProp(code: string, propName: string): unknown {
|
|
7
|
+
const openBraceMatch = code.match(new RegExp(`${propName}\\s*=\\s*\\{`));
|
|
8
|
+
if (!openBraceMatch || openBraceMatch.index === undefined) return undefined;
|
|
9
|
+
|
|
10
|
+
const openIndex = openBraceMatch.index + openBraceMatch[0].length - 1;
|
|
11
|
+
const closeIndex = findMatchingBrace(code, openIndex);
|
|
12
|
+
if (closeIndex === -1) return undefined;
|
|
13
|
+
|
|
14
|
+
const expr = code.slice(openIndex + 1, closeIndex);
|
|
15
|
+
try {
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
17
|
+
return new Function(`return (${expr})`)();
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 从 openIndex(必须指向 '{')开始,找到与之匹配的 '}' 的下标;跳过字符串内的花括号 */
|
|
24
|
+
function findMatchingBrace(code: string, openIndex: number): number {
|
|
25
|
+
let depth = 0;
|
|
26
|
+
let inString: '"' | "'" | null = null;
|
|
27
|
+
|
|
28
|
+
for (let i = openIndex; i < code.length; i++) {
|
|
29
|
+
const ch = code[i];
|
|
30
|
+
|
|
31
|
+
if (inString) {
|
|
32
|
+
if (ch === '\\') {
|
|
33
|
+
i++; // 跳过转义字符,避免把 \" 误判为字符串结束
|
|
34
|
+
} else if (ch === inString) {
|
|
35
|
+
inString = null;
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (ch === '"' || ch === "'") {
|
|
41
|
+
inString = ch;
|
|
42
|
+
} else if (ch === '{') {
|
|
43
|
+
depth++;
|
|
44
|
+
} else if (ch === '}') {
|
|
45
|
+
depth--;
|
|
46
|
+
if (depth === 0) return i;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return -1;
|
|
50
|
+
}
|