@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.
@@ -12,10 +12,12 @@ import {
12
12
  PlusOutlined,
13
13
  PlusCircleOutlined,
14
14
  MinusCircleOutlined,
15
+ ArrowLeftOutlined,
15
16
  } from '@ant-design/icons';
16
17
  import CanvasCell from './CanvasCell';
17
18
  import CodeFloatWindow from './CodeFloatWindow';
18
19
  import CanvasToolbar from './CanvasToolbar';
20
+ import CanvasComponentLibrary from './CanvasComponentLibrary';
19
21
  import { CANVAS_COMPONENTS, CANVAS_DEFAULTS } from './canvasDefaults';
20
22
  import { CANVAS_CONFIG_REGISTRY } from './canvasConfigRegistry';
21
23
  import { useAgent } from '@/components/agentPanel/AgentContext';
@@ -144,6 +146,7 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
144
146
  const [isPreview, setIsPreview] = useState(false);
145
147
  const [editingId, setEditingId] = useState<string | null>(null);
146
148
  const [isDragging, setIsDragging] = useState(false);
149
+ const [libraryCollapsed, setLibraryCollapsed] = useState(false);
147
150
  const isAgentAllowed = useIsAgentAllowed();
148
151
  const [configModal, setConfigModal] = useState<{
149
152
  id: string;
@@ -295,7 +298,7 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
295
298
  );
296
299
 
297
300
  // 添加组件
298
- const addComponent = useCallback((type: string) => {
301
+ const addComponent = useCallback((type: string, customCode?: string) => {
299
302
  const { items, layout, onBothChange } = liveRef.current;
300
303
  const def = CANVAS_DEFAULTS[type];
301
304
  if (!def) return;
@@ -304,10 +307,17 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
304
307
  const h = def.layout.h;
305
308
  // 映射实际 componentType(BarChart/LineChart/PieChart → MultiChart)
306
309
  const actualType = COMPONENT_TYPE_MAP[type] ?? type;
310
+ // 优先使用 variant 代码(来自点击/拖拽指定的 variant),fallback 到默认 code
311
+ const baseCode = customCode
312
+ ? customCode.replace(
313
+ /testId="demo-([^"]+)"/g,
314
+ `testId="canvas-$1-${Date.now()}"`,
315
+ )
316
+ : def.code;
307
317
  onBothChange(
308
318
  [
309
319
  ...items,
310
- { id, componentType: actualType, code: syncCodeHeight(def.code, h) },
320
+ { id, componentType: actualType, code: syncCodeHeight(baseCode, h) },
311
321
  ],
312
322
  [
313
323
  ...layout,
@@ -614,812 +624,883 @@ const CanvasPage: React.FC<CanvasPageProps> = ({
614
624
  />
615
625
  )}
616
626
 
617
- {/* ── 画布 ── */}
618
- <div
619
- ref={containerRef as React.RefObject<HTMLDivElement>}
620
- onClick={handleCanvasClick}
621
- style={{
622
- flex: 1,
623
- overflow: isPreview ? 'visible' : 'auto',
624
- position: 'relative',
625
- background: isDragging
626
- ? `
627
- radial-gradient(circle, #1677ff 1.5px, transparent 1.5px) 0 0 / 90px 90px,
628
- radial-gradient(circle, #1677ff 0.8px, transparent 0.8px) 0 0 / 18px 18px,
629
- #f0f2f5
630
- `
631
- : '#f0f2f5',
632
- transition: 'background 0.15s',
633
- }}
634
- >
635
- {/* 空画布提示 */}
636
- {items.length === 0 && (
637
- <div
638
- style={{
639
- position: 'absolute',
640
- inset: 0,
641
- display: 'flex',
642
- flexDirection: 'column',
643
- alignItems: 'center',
644
- justifyContent: 'center',
645
- gap: 12,
646
- userSelect: 'none',
647
- pointerEvents: 'none',
648
- }}
649
- >
650
- <PlusCircleOutlined style={{ fontSize: 48, color: '#d9d9d9' }} />
651
- <span style={{ fontSize: 16, color: '#8c8c8c' }}>
652
- {t('canvas.emptyHint')}
653
- </span>
654
- <span style={{ fontSize: 13, color: '#bfbfbf' }}>
655
- 或右键画布添加组件
656
- </span>
657
- </div>
627
+ {/* ── 主体:左侧组件库 + 右侧画布 ── */}
628
+ <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
629
+ {!isPreview && isAgentAllowed && (
630
+ <CanvasComponentLibrary
631
+ collapsed={libraryCollapsed}
632
+ onToggleCollapsed={() => setLibraryCollapsed((v) => !v)}
633
+ onAdd={addComponent}
634
+ />
658
635
  )}
659
636
 
660
- {/* 网格布局 */}
661
- {mounted && (
662
- <div style={{ maxWidth: 1600, margin: '0 auto', width: '100%' }}>
663
- <GridLayout
664
- className="layout"
665
- layout={layout}
666
- width={Math.min(containerWidth, 1600)}
637
+ {/* ── 画布 ── */}
638
+ <div
639
+ ref={containerRef as React.RefObject<HTMLDivElement>}
640
+ onClick={handleCanvasClick}
641
+ style={{
642
+ flex: 1,
643
+ overflow: isPreview ? 'visible' : 'auto',
644
+ position: 'relative',
645
+ background: isDragging
646
+ ? `
647
+ radial-gradient(circle, #1677ff 1.5px, transparent 1.5px) 0 0 / 90px 90px,
648
+ radial-gradient(circle, #1677ff 0.8px, transparent 0.8px) 0 0 / 18px 18px,
649
+ #f0f2f5
650
+ `
651
+ : '#f0f2f5',
652
+ transition: 'background 0.15s',
653
+ }}
654
+ >
655
+ {/* 空画布提示 */}
656
+ {items.length === 0 && (
657
+ <div
667
658
  style={{
668
- minHeight: Math.max(
669
- // 根据 layout 内容底部动态延伸 + 额外空间
670
- layout.reduce(
671
- (max, item) =>
672
- Math.max(
673
- max,
674
- (item.y + item.h) * (ROW_HEIGHT + MARGIN_Y),
675
- ),
676
- 0,
677
- ) + (isDragging ? 600 : 300),
678
- // 至少占满一屏
679
- window.innerHeight - 200,
680
- ),
659
+ position: 'absolute',
660
+ inset: 0,
661
+ display: 'flex',
662
+ alignItems: 'center',
663
+ justifyContent: 'center',
664
+ userSelect: 'none',
665
+ pointerEvents: 'none',
681
666
  }}
682
- gridConfig={{
683
- cols: COLS,
684
- rowHeight: ROW_HEIGHT,
685
- margin: [MARGIN_Y, MARGIN_Y] as const,
667
+ >
668
+ <style>{`
669
+ @keyframes canvas-arrow-nudge {
670
+ 0%, 100% { transform: translateX(0); }
671
+ 50% { transform: translateX(-8px); }
672
+ }
673
+ .canvas-empty-arrow {
674
+ animation: canvas-arrow-nudge 1.4s ease-in-out infinite;
675
+ }
676
+ `}</style>
677
+ <div
678
+ style={{
679
+ display: 'flex',
680
+ alignItems: 'center',
681
+ gap: 16,
682
+ }}
683
+ >
684
+ {!isPreview && isAgentAllowed && !libraryCollapsed && (
685
+ <ArrowLeftOutlined
686
+ className="canvas-empty-arrow"
687
+ style={{ fontSize: 28, color: '#4361ee' }}
688
+ />
689
+ )}
690
+ <div
691
+ style={{
692
+ display: 'flex',
693
+ flexDirection: 'column',
694
+ alignItems: 'center',
695
+ gap: 8,
696
+ padding: '24px 32px',
697
+ background: '#fff',
698
+ border: '1px dashed #c8d3ff',
699
+ borderRadius: 12,
700
+ boxShadow: '0 2px 12px rgba(67,97,238,0.06)',
701
+ }}
702
+ >
703
+ <PlusCircleOutlined
704
+ style={{ fontSize: 36, color: '#4361ee' }}
705
+ />
706
+ <span
707
+ style={{ fontSize: 15, color: '#333', fontWeight: 500 }}
708
+ >
709
+ {t('canvas.emptyHint')}
710
+ </span>
711
+ <span style={{ fontSize: 12, color: '#8c8c8c' }}>
712
+ 在左侧组件库 <b style={{ color: '#4361ee' }}>点击</b> 或{' '}
713
+ <b style={{ color: '#4361ee' }}>拖拽</b> 任一组件
714
+ </span>
715
+ </div>
716
+ </div>
717
+ </div>
718
+ )}
719
+
720
+ {/* 网格布局 */}
721
+ {mounted && (
722
+ <div style={{ maxWidth: 1600, margin: '0 auto', width: '100%' }}>
723
+ <GridLayout
724
+ className="layout"
725
+ layout={layout}
726
+ width={Math.min(containerWidth, 1600)}
727
+ style={{
728
+ minHeight: Math.max(
729
+ // 根据 layout 内容底部动态延伸 + 额外空间
730
+ layout.reduce(
731
+ (max, item) =>
732
+ Math.max(
733
+ max,
734
+ (item.y + item.h) * (ROW_HEIGHT + MARGIN_Y),
735
+ ),
736
+ 0,
737
+ ) + (isDragging ? 600 : 300),
738
+ // 至少占满一屏
739
+ window.innerHeight - 200,
740
+ ),
741
+ }}
742
+ gridConfig={{
743
+ cols: COLS,
744
+ rowHeight: ROW_HEIGHT,
745
+ margin: [MARGIN_Y, MARGIN_Y] as const,
746
+ }}
747
+ dragConfig={{
748
+ enabled: !isPreview && isAgentAllowed,
749
+ handle: '.canvas-item-drag',
750
+ }}
751
+ resizeConfig={{
752
+ enabled: !isPreview && isAgentAllowed,
753
+ }}
754
+ dropConfig={
755
+ isPreview || !isAgentAllowed ? undefined : dropConfig
756
+ }
757
+ onDrop={isPreview || !isAgentAllowed ? undefined : handleDrop}
758
+ compactor={sectionCompactor}
759
+ onResizeStop={handleResizeStop}
760
+ onDragStart={() => setIsDragging(true)}
761
+ onDragStop={handleDragStop}
762
+ >
763
+ {items.map((item) => (
764
+ <CanvasCell
765
+ key={item.id}
766
+ className="canvas-item-drag"
767
+ style={{ zIndex: item.componentType === 'Section' ? 0 : 1 }}
768
+ componentType={item.componentType}
769
+ isPreview={isPreview}
770
+ data-grid-id={item.id}
771
+ data-agent-type="canvas-component"
772
+ data-agent-resource="canvas"
773
+ data-agent-meta={JSON.stringify({
774
+ componentType: item.componentType,
775
+ canvasItemId: item.id,
776
+ code: item.code,
777
+ })}
778
+ code={item.code}
779
+ selected={selectedId === item.id}
780
+ onClick={() => setSelectedId(item.id)}
781
+ onDoubleClick={() => setEditingId(item.id)}
782
+ onContextMenu={(e: React.MouseEvent) =>
783
+ handleItemContextMenu(e, item.id)
784
+ }
785
+ onDuplicate={() => duplicateComponent(item.id)}
786
+ onDelete={() => removeComponent(item.id)}
787
+ onMore={(e: React.MouseEvent) =>
788
+ handleItemContextMenu(e, item.id)
789
+ }
790
+ />
791
+ ))}
792
+ </GridLayout>
793
+ </div>
794
+ )}
795
+
796
+ {/* 预览模式退出按钮 */}
797
+ {isPreview && (
798
+ <Button
799
+ onClick={() => setIsPreview(false)}
800
+ style={{
801
+ position: 'fixed',
802
+ top: 68,
803
+ left: '58%',
804
+ transform: 'translateX(-50%)',
805
+ zIndex: 1000,
806
+ borderRadius: 8,
807
+ background: '#fff',
808
+ border: '1px solid #e0e0e0',
809
+ boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
810
+ fontWeight: 500,
811
+ color: '#555',
686
812
  }}
687
- dragConfig={{
688
- enabled: !isPreview && isAgentAllowed,
689
- handle: '.canvas-item-drag',
813
+ >
814
+ {t('canvas.exitPreview')}
815
+ </Button>
816
+ )}
817
+
818
+ {/* 组件右键菜单 */}
819
+ {itemMenu && (
820
+ <Dropdown
821
+ menu={{
822
+ items: itemMenuItems,
823
+ style: { maxHeight: 'calc(100vh - 80px)', overflowY: 'auto' },
690
824
  }}
691
- resizeConfig={{
692
- enabled: !isPreview && isAgentAllowed,
825
+ open
826
+ trigger={['click']}
827
+ onOpenChange={(open) => {
828
+ if (!open) closeItemMenu();
693
829
  }}
694
- dropConfig={isPreview || !isAgentAllowed ? undefined : dropConfig}
695
- onDrop={isPreview || !isAgentAllowed ? undefined : handleDrop}
696
- compactor={sectionCompactor}
697
- onResizeStop={handleResizeStop}
698
- onDragStart={() => setIsDragging(true)}
699
- onDragStop={handleDragStop}
700
830
  >
701
- {items.map((item) => (
702
- <CanvasCell
703
- key={item.id}
704
- className="canvas-item-drag"
705
- style={{ zIndex: item.componentType === 'Section' ? 0 : 1 }}
706
- componentType={item.componentType}
707
- isPreview={isPreview}
708
- data-grid-id={item.id}
709
- data-agent-type="canvas-component"
710
- data-agent-resource="canvas"
711
- data-agent-meta={JSON.stringify({
712
- componentType: item.componentType,
713
- canvasItemId: item.id,
714
- code: item.code,
715
- })}
831
+ <div
832
+ style={{
833
+ position: 'fixed',
834
+ left: itemMenu.x,
835
+ top: itemMenu.y,
836
+ width: 1,
837
+ height: 1,
838
+ }}
839
+ />
840
+ </Dropdown>
841
+ )}
842
+
843
+ {/* 组件配置 Modal */}
844
+ {configModal &&
845
+ (() => {
846
+ const ConfigModal = CANVAS_CONFIG_REGISTRY[
847
+ configModal.componentType
848
+ ] as React.FC<CanvasConfigModalProps> | undefined;
849
+ const item = items.find((i) => i.id === configModal.id);
850
+ if (!ConfigModal || !item) return null;
851
+ return (
852
+ <ConfigModal
716
853
  code={item.code}
717
- selected={selectedId === item.id}
718
- onClick={() => setSelectedId(item.id)}
719
- onDoubleClick={() => setEditingId(item.id)}
720
- onContextMenu={(e: React.MouseEvent) =>
721
- handleItemContextMenu(e, item.id)
722
- }
854
+ onConfirm={(newCode) => {
855
+ updateCode(configModal.id, newCode);
856
+ setConfigModal(null);
857
+ }}
858
+ onCancel={() => setConfigModal(null)}
723
859
  />
724
- ))}
725
- </GridLayout>
726
- </div>
727
- )}
728
-
729
- {/* 预览模式退出按钮 */}
730
- {isPreview && (
731
- <Button
732
- onClick={() => setIsPreview(false)}
733
- style={{
734
- position: 'fixed',
735
- top: 68,
736
- left: '58%',
737
- transform: 'translateX(-50%)',
738
- zIndex: 1000,
739
- borderRadius: 8,
740
- background: '#fff',
741
- border: '1px solid #e0e0e0',
742
- boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
743
- fontWeight: 500,
744
- color: '#555',
745
- }}
746
- >
747
- {t('canvas.exitPreview')}
748
- </Button>
749
- )}
750
-
751
- {/* 组件右键菜单 */}
752
- {itemMenu && (
753
- <Dropdown
754
- menu={{ items: itemMenuItems }}
755
- open
756
- trigger={['click']}
757
- onOpenChange={(open) => {
758
- if (!open) closeItemMenu();
759
- }}
760
- >
761
- <div
762
- style={{
763
- position: 'fixed',
764
- left: itemMenu.x,
765
- top: itemMenu.y,
766
- width: 1,
767
- height: 1,
768
- }}
860
+ );
861
+ })()}
862
+
863
+ {/* 浮动代码窗口 */}
864
+ {!isPreview && editingItem && editingDef && (
865
+ <CodeFloatWindow
866
+ componentType={editingItem.componentType}
867
+ imports={editingDef.imports}
868
+ code={editingItem.code}
869
+ props={editingDef.props}
870
+ variants={editingDef.variants}
871
+ onChange={(code) => updateCode(editingItem.id, code)}
872
+ onClose={() => setEditingId(null)}
769
873
  />
770
- </Dropdown>
771
- )}
772
-
773
- {/* 组件配置 Modal */}
774
- {configModal &&
775
- (() => {
776
- const ConfigModal = CANVAS_CONFIG_REGISTRY[
777
- configModal.componentType
778
- ] as React.FC<CanvasConfigModalProps> | undefined;
779
- const item = items.find((i) => i.id === configModal.id);
780
- if (!ConfigModal || !item) return null;
781
- return (
782
- <ConfigModal
783
- code={item.code}
784
- onConfirm={(newCode) => {
785
- updateCode(configModal.id, newCode);
786
- setConfigModal(null);
787
- }}
788
- onCancel={() => setConfigModal(null)}
789
- />
790
- );
791
- })()}
792
-
793
- {/* 浮动代码窗口 */}
794
- {!isPreview && editingItem && editingDef && (
795
- <CodeFloatWindow
796
- componentType={editingItem.componentType}
797
- imports={editingDef.imports}
798
- code={editingItem.code}
799
- props={editingDef.props}
800
- variants={editingDef.variants}
801
- onChange={(code) => updateCode(editingItem.id, code)}
802
- onClose={() => setEditingId(null)}
803
- />
804
- )}
874
+ )}
805
875
 
806
- {/* NumCard 修改数据来源 Modal */}
807
- <NumCardDataSourceModal
808
- open={
809
- !!dataSourceModal && dataSourceModal.componentType === 'NumCard'
810
- }
811
- code={dataSourceModal?.code ?? ''}
812
- onConfirm={({
813
- wantChart,
814
- chartRange,
815
- wantComparisons,
816
- comparisonLabels,
817
- dataDescription,
818
- code: origCode,
819
- }) => {
820
- if (!dataSourceModal) return;
821
- let newCode = origCode;
822
-
823
- // 处理折线图
824
- const curHasChart = /variant\s*=\s*"withChart"/.test(newCode);
825
- if (wantChart && !curHasChart) {
826
- // 添加 withChart
827
- if (/variant\s*=\s*"[^"]*"/.test(newCode)) {
828
- newCode = newCode.replace(
829
- /variant\s*=\s*"[^"]*"/,
830
- 'variant="withChart"',
831
- );
832
- } else {
876
+ {/* NumCard 修改数据来源 Modal */}
877
+ <NumCardDataSourceModal
878
+ open={
879
+ !!dataSourceModal && dataSourceModal.componentType === 'NumCard'
880
+ }
881
+ code={dataSourceModal?.code ?? ''}
882
+ onConfirm={({
883
+ wantChart,
884
+ chartRange,
885
+ wantComparisons,
886
+ comparisonLabels,
887
+ dataDescription,
888
+ code: origCode,
889
+ }) => {
890
+ if (!dataSourceModal) return;
891
+ let newCode = origCode;
892
+
893
+ // 处理折线图
894
+ const curHasChart = /variant\s*=\s*"withChart"/.test(newCode);
895
+ if (wantChart && !curHasChart) {
896
+ // 添加 withChart
897
+ if (/variant\s*=\s*"[^"]*"/.test(newCode)) {
898
+ newCode = newCode.replace(
899
+ /variant\s*=\s*"[^"]*"/,
900
+ 'variant="withChart"',
901
+ );
902
+ } else {
903
+ newCode = newCode.replace(
904
+ /(<NumCard)/,
905
+ '$1\n variant="withChart"',
906
+ );
907
+ }
908
+ if (!/chartData\s*=/.test(newCode)) {
909
+ const tpl = `chartData={[]}\n chartTestId="canvas-numcard-chart"`;
910
+ newCode = newCode.replace(
911
+ /([ \t]*)(testId\s*=)/,
912
+ `$1${tpl}\n$1$2`,
913
+ );
914
+ }
915
+ } else if (!wantChart && curHasChart) {
916
+ // 移除 withChart
833
917
  newCode = newCode.replace(
834
- /(<NumCard)/,
835
- '$1\n variant="withChart"',
918
+ /variant\s*=\s*"withChart"/,
919
+ 'variant="default"',
836
920
  );
837
- }
838
- if (!/chartData\s*=/.test(newCode)) {
839
- const tpl = `chartData={[]}\n chartTestId="canvas-numcard-chart"`;
840
921
  newCode = newCode.replace(
841
- /([ \t]*)(testId\s*=)/,
842
- `$1${tpl}\n$1$2`,
922
+ /\s*chartData\s*=\s*\{[\s\S]*?\]\s*\}/,
923
+ '',
843
924
  );
925
+ newCode = newCode.replace(/\s*chartTestId\s*=\s*"[^"]*"/, '');
844
926
  }
845
- } else if (!wantChart && curHasChart) {
846
- // 移除 withChart
847
- newCode = newCode.replace(
848
- /variant\s*=\s*"withChart"/,
849
- 'variant="default"',
850
- );
851
- newCode = newCode.replace(
852
- /\s*chartData\s*=\s*\{[\s\S]*?\]\s*\}/,
853
- '',
854
- );
855
- newCode = newCode.replace(/\s*chartTestId\s*=\s*"[^"]*"/, '');
856
- }
857
927
 
858
- // 处理比较数据
859
- const curHasComp = /comparisons\s*=\s*\{\[/.test(newCode);
860
- if (wantComparisons && comparisonLabels.length > 0) {
861
- const compStr = comparisonLabels
862
- .map((l) => `{ label: "${l}", value: "0%", isUp: true }`)
863
- .join(',\n ');
864
- const compProp = `comparisons={[\n ${compStr},\n ]}`;
865
- if (curHasComp) {
866
- newCode = newCode.replace(
867
- /comparisons\s*=\s*\{\[[\s\S]*?\]\}/,
868
- compProp,
869
- );
870
- } else {
871
- if (/testId\s*=/.test(newCode)) {
928
+ // 处理比较数据
929
+ const curHasComp = /comparisons\s*=\s*\{\[/.test(newCode);
930
+ if (wantComparisons && comparisonLabels.length > 0) {
931
+ const compStr = comparisonLabels
932
+ .map((l) => `{ label: "${l}", value: "0%", isUp: true }`)
933
+ .join(',\n ');
934
+ const compProp = `comparisons={[\n ${compStr},\n ]}`;
935
+ if (curHasComp) {
872
936
  newCode = newCode.replace(
873
- /([ \t]*)(testId\s*=)/,
874
- `$1${compProp}\n$1$2`,
937
+ /comparisons\s*=\s*\{\[[\s\S]*?\]\}/,
938
+ compProp,
875
939
  );
876
940
  } else {
877
- newCode = newCode.replace(/\/>/, ` ${compProp}\n/>`);
941
+ if (/testId\s*=/.test(newCode)) {
942
+ newCode = newCode.replace(
943
+ /([ \t]*)(testId\s*=)/,
944
+ `$1${compProp}\n$1$2`,
945
+ );
946
+ } else {
947
+ newCode = newCode.replace(/\/>/, ` ${compProp}\n/>`);
948
+ }
878
949
  }
950
+ } else if (!wantComparisons && curHasComp) {
951
+ newCode = newCode.replace(
952
+ /\s*comparisons\s*=\s*\{\[[\s\S]*?\]\}/,
953
+ '',
954
+ );
879
955
  }
880
- } else if (!wantComparisons && curHasComp) {
881
- newCode = newCode.replace(
882
- /\s*comparisons\s*=\s*\{\[[\s\S]*?\]\}/,
883
- '',
884
- );
885
- }
886
956
 
887
- updateCode(dataSourceModal.id, newCode);
888
- setDataSourceModal(null);
957
+ updateCode(dataSourceModal.id, newCode);
958
+ setDataSourceModal(null);
889
959
 
890
- // 构造描述发给 agent
891
- const parts: string[] = [];
892
- if (dataDescription) parts.push(dataDescription);
893
- if (wantChart)
894
- parts.push(`需要右侧折线图数据,显示范围:${chartRange}`);
895
- if (wantComparisons && comparisonLabels.length > 0) {
896
- parts.push(`需要比较数据:${comparisonLabels.join('、')}`);
897
- }
898
- const userPrompt = parts.join('。');
899
- triggerDataSourceAgent(
900
- dataSourceModal.id,
901
- dataSourceModal.componentType,
902
- newCode,
903
- userPrompt || undefined,
904
- 'canvas-sql-query',
905
- );
906
- }}
907
- onCancel={() => setDataSourceModal(null)}
908
- />
909
-
910
- {/* Table 修改数据来源 Modal */}
911
- <TableDataSourceModal
912
- open={!!dataSourceModal && dataSourceModal.componentType === 'Table'}
913
- code={dataSourceModal?.code ?? ''}
914
- onConfirm={({
915
- columns,
916
- wantPagination,
917
- pageSize,
918
- dataDescription,
919
- code: origCode,
920
- }) => {
921
- if (!dataSourceModal) return;
922
- let newCode = origCode;
923
-
924
- // 根据用户选择更新 columns
925
- const colsStr = columns
926
- .map(
927
- (c) => ` { title: "${c}", dataIndex: "${c}", key: "${c}" }`,
928
- )
929
- .join(',\n');
930
- if (/columns\s*=\s*\{\[/.test(newCode)) {
931
- newCode = newCode.replace(
932
- /columns\s*=\s*\{\[[\s\S]*?\]\}/,
933
- `columns={[\n${colsStr},\n ]}`,
960
+ // 构造描述发给 agent
961
+ const parts: string[] = [];
962
+ if (dataDescription) parts.push(dataDescription);
963
+ if (wantChart)
964
+ parts.push(`需要右侧折线图数据,显示范围:${chartRange}`);
965
+ if (wantComparisons && comparisonLabels.length > 0) {
966
+ parts.push(`需要比较数据:${comparisonLabels.join('、')}`);
967
+ }
968
+ const userPrompt = parts.join('。');
969
+ triggerDataSourceAgent(
970
+ dataSourceModal.id,
971
+ dataSourceModal.componentType,
972
+ newCode,
973
+ userPrompt || undefined,
974
+ 'canvas-sql-query',
934
975
  );
935
- }
976
+ }}
977
+ onCancel={() => setDataSourceModal(null)}
978
+ />
936
979
 
937
- // 处理分页
938
- const curHasPagination = /pagination\s*=\s*\{\{/.test(newCode);
939
- if (wantPagination && !curHasPagination) {
940
- const paginationTpl = `pagination={{ current: 1, pageSize: ${pageSize}, total: 0 }}`;
941
- if (/testId\s*=/.test(newCode)) {
980
+ {/* Table 修改数据来源 Modal */}
981
+ <TableDataSourceModal
982
+ open={
983
+ !!dataSourceModal && dataSourceModal.componentType === 'Table'
984
+ }
985
+ code={dataSourceModal?.code ?? ''}
986
+ onConfirm={({
987
+ columns,
988
+ wantPagination,
989
+ pageSize,
990
+ dataDescription,
991
+ code: origCode,
992
+ }) => {
993
+ if (!dataSourceModal) return;
994
+ let newCode = origCode;
995
+
996
+ // 根据用户选择更新 columns
997
+ const colsStr = columns
998
+ .map(
999
+ (c) =>
1000
+ ` { title: "${c}", dataIndex: "${c}", key: "${c}" }`,
1001
+ )
1002
+ .join(',\n');
1003
+ if (/columns\s*=\s*\{\[/.test(newCode)) {
942
1004
  newCode = newCode.replace(
943
- /([ \t]*)(testId\s*=)/,
944
- `$1${paginationTpl}\n$1$2`,
1005
+ /columns\s*=\s*\{\[[\s\S]*?\]\}/,
1006
+ `columns={[\n${colsStr},\n ]}`,
945
1007
  );
946
- } else {
947
- newCode = newCode.replace(/\/>/, ` ${paginationTpl}\n/>`);
948
1008
  }
949
- } else if (wantPagination && curHasPagination) {
950
- // 更新已有的 pageSize
951
- newCode = newCode.replace(
952
- /pageSize\s*:\s*\d+/,
953
- `pageSize: ${pageSize}`,
954
- );
955
- } else if (!wantPagination && curHasPagination) {
956
- newCode = newCode.replace(
957
- /\s*pagination\s*=\s*\{\{[\s\S]*?\}\}/,
958
- '',
959
- );
960
- newCode = newCode.replace(/\s*entityName\s*=\s*"[^"]*"/, '');
961
- }
962
-
963
- updateCode(dataSourceModal.id, newCode);
964
- setDataSourceModal(null);
965
-
966
- // 构造描述发给 agent
967
- const parts: string[] = [];
968
- if (dataDescription) parts.push(dataDescription);
969
- parts.push(`表格列:${columns.join('、')}`);
970
- if (wantPagination) parts.push(`需要分页,每页 ${pageSize} 条`);
971
- const userPrompt = parts.join('。');
972
- triggerDataSourceAgent(
973
- dataSourceModal.id,
974
- dataSourceModal.componentType,
975
- newCode,
976
- userPrompt,
977
- 'canvas-sql-query',
978
- );
979
- }}
980
- onCancel={() => setDataSourceModal(null)}
981
- />
982
1009
 
983
- {/* BarChart 修改数据来源 Modal */}
984
- <BarChartDataSourceModal
985
- open={
986
- !!dataSourceModal && dataSourceModal.componentType === 'BarChart'
987
- }
988
- code={dataSourceModal?.code ?? ''}
989
- onConfirm={(result) => {
990
- if (!dataSourceModal) return;
991
- let newCode = result.code;
992
-
993
- // 修改 direction
994
- if (result.direction === 'horizontal') {
995
- if (!/direction\s*=/.test(newCode)) {
1010
+ // 处理分页
1011
+ const curHasPagination = /pagination\s*=\s*\{\{/.test(newCode);
1012
+ if (wantPagination && !curHasPagination) {
1013
+ const paginationTpl = `pagination={{ current: 1, pageSize: ${pageSize}, total: 0 }}`;
996
1014
  if (/testId\s*=/.test(newCode)) {
997
1015
  newCode = newCode.replace(
998
1016
  /([ \t]*)(testId\s*=)/,
999
- `$1direction="horizontal"\n$1$2`,
1017
+ `$1${paginationTpl}\n$1$2`,
1000
1018
  );
1001
1019
  } else {
1002
- newCode = newCode.replace(
1003
- /\/>/,
1004
- ` direction="horizontal"\n/>`,
1005
- );
1020
+ newCode = newCode.replace(/\/>/, ` ${paginationTpl}\n/>`);
1006
1021
  }
1007
- } else {
1022
+ } else if (wantPagination && curHasPagination) {
1023
+ // 更新已有的 pageSize
1024
+ newCode = newCode.replace(
1025
+ /pageSize\s*:\s*\d+/,
1026
+ `pageSize: ${pageSize}`,
1027
+ );
1028
+ } else if (!wantPagination && curHasPagination) {
1008
1029
  newCode = newCode.replace(
1009
- /direction\s*=\s*"[^"]*"/,
1010
- 'direction="horizontal"',
1030
+ /\s*pagination\s*=\s*\{\{[\s\S]*?\}\}/,
1031
+ '',
1011
1032
  );
1033
+ newCode = newCode.replace(/\s*entityName\s*=\s*"[^"]*"/, '');
1012
1034
  }
1013
- } else {
1014
- // vertical 是默认值,移除 direction prop
1015
- newCode = newCode.replace(/\s*direction\s*=\s*"[^"]*"/, '');
1016
- }
1017
1035
 
1018
- // 修改 showGrid
1019
- if (!result.showGrid) {
1020
- if (!/showGrid\s*=/.test(newCode)) {
1021
- if (/testId\s*=/.test(newCode)) {
1036
+ updateCode(dataSourceModal.id, newCode);
1037
+ setDataSourceModal(null);
1038
+
1039
+ // 构造描述发给 agent
1040
+ const parts: string[] = [];
1041
+ if (dataDescription) parts.push(dataDescription);
1042
+ parts.push(`表格列:${columns.join('、')}`);
1043
+ if (wantPagination) parts.push(`需要分页,每页 ${pageSize} 条`);
1044
+ const userPrompt = parts.join('。');
1045
+ triggerDataSourceAgent(
1046
+ dataSourceModal.id,
1047
+ dataSourceModal.componentType,
1048
+ newCode,
1049
+ userPrompt,
1050
+ 'canvas-sql-query',
1051
+ );
1052
+ }}
1053
+ onCancel={() => setDataSourceModal(null)}
1054
+ />
1055
+
1056
+ {/* BarChart 修改数据来源 Modal */}
1057
+ <BarChartDataSourceModal
1058
+ open={
1059
+ !!dataSourceModal && dataSourceModal.componentType === 'BarChart'
1060
+ }
1061
+ code={dataSourceModal?.code ?? ''}
1062
+ onConfirm={(result) => {
1063
+ if (!dataSourceModal) return;
1064
+ let newCode = result.code;
1065
+
1066
+ // 修改 direction
1067
+ if (result.direction === 'horizontal') {
1068
+ if (!/direction\s*=/.test(newCode)) {
1069
+ if (/testId\s*=/.test(newCode)) {
1070
+ newCode = newCode.replace(
1071
+ /([ \t]*)(testId\s*=)/,
1072
+ `$1direction="horizontal"\n$1$2`,
1073
+ );
1074
+ } else {
1075
+ newCode = newCode.replace(
1076
+ /\/>/,
1077
+ ` direction="horizontal"\n/>`,
1078
+ );
1079
+ }
1080
+ } else {
1022
1081
  newCode = newCode.replace(
1023
- /([ \t]*)(testId\s*=)/,
1024
- `$1showGrid={false}\n$1$2`,
1082
+ /direction\s*=\s*"[^"]*"/,
1083
+ 'direction="horizontal"',
1025
1084
  );
1026
- } else {
1027
- newCode = newCode.replace(/\/>/, ` showGrid={false}\n/>`);
1028
1085
  }
1086
+ } else {
1087
+ // vertical 是默认值,移除 direction prop
1088
+ newCode = newCode.replace(/\s*direction\s*=\s*"[^"]*"/, '');
1029
1089
  }
1030
- } else {
1031
- newCode = newCode.replace(/\s*showGrid\s*=\s*\{false\}/, '');
1032
- }
1033
1090
 
1034
- // 修改 showLabel
1035
- if (!result.showLabel) {
1036
- if (!/showLabel\s*=/.test(newCode)) {
1037
- if (/testId\s*=/.test(newCode)) {
1038
- newCode = newCode.replace(
1039
- /([ \t]*)(testId\s*=)/,
1040
- `$1showLabel={false}\n$1$2`,
1041
- );
1042
- } else {
1043
- newCode = newCode.replace(/\/>/, ` showLabel={false}\n/>`);
1091
+ // 修改 showGrid
1092
+ if (!result.showGrid) {
1093
+ if (!/showGrid\s*=/.test(newCode)) {
1094
+ if (/testId\s*=/.test(newCode)) {
1095
+ newCode = newCode.replace(
1096
+ /([ \t]*)(testId\s*=)/,
1097
+ `$1showGrid={false}\n$1$2`,
1098
+ );
1099
+ } else {
1100
+ newCode = newCode.replace(/\/>/, ` showGrid={false}\n/>`);
1101
+ }
1044
1102
  }
1103
+ } else {
1104
+ newCode = newCode.replace(/\s*showGrid\s*=\s*\{false\}/, '');
1045
1105
  }
1046
- } else {
1047
- newCode = newCode.replace(/\s*showLabel\s*=\s*\{false\}/, '');
1048
- }
1049
1106
 
1050
- // 更新画布
1051
- updateCode(dataSourceModal.id, newCode);
1052
- setDataSourceModal(null);
1053
-
1054
- // 构造 prompt 并触发 agent
1055
- const dirLabel =
1056
- result.direction === 'horizontal' ? '横向' : '纵向';
1057
- const parts: string[] = [];
1058
- if (result.dataDescription) parts.push(result.dataDescription);
1059
- parts.push(`方向: ${dirLabel}`);
1060
- if (!result.showGrid) parts.push('隐藏网格线');
1061
- if (!result.showLabel) parts.push('隐藏数据标签');
1062
- const userPrompt = parts.join('。');
1063
- triggerDataSourceAgent(
1064
- dataSourceModal.id,
1065
- 'BarChart',
1066
- newCode,
1067
- userPrompt,
1068
- 'canvas-sql-query',
1069
- );
1070
- }}
1071
- onCancel={() => setDataSourceModal(null)}
1072
- />
1073
-
1074
- <LineChartDataSourceModal
1075
- open={
1076
- !!dataSourceModal && dataSourceModal.componentType === 'LineChart'
1077
- }
1078
- code={dataSourceModal?.code ?? ''}
1079
- onConfirm={(result) => {
1080
- if (!dataSourceModal) return;
1081
- let newCode = result.code;
1082
-
1083
- // ── legendShow ──
1084
- if (!result.legendShow) {
1085
- if (!/legendShow\s*=\s*\{/.test(newCode)) {
1086
- if (/testId\s*=/.test(newCode)) {
1087
- newCode = newCode.replace(
1088
- /([ \t]*)(testId\s*=)/,
1089
- `$1legendShow={false}\n$1$2`,
1090
- );
1091
- } else {
1092
- newCode = newCode.replace(/\/>/, ` legendShow={false}\n/>`);
1107
+ // 修改 showLabel
1108
+ if (!result.showLabel) {
1109
+ if (!/showLabel\s*=/.test(newCode)) {
1110
+ if (/testId\s*=/.test(newCode)) {
1111
+ newCode = newCode.replace(
1112
+ /([ \t]*)(testId\s*=)/,
1113
+ `$1showLabel={false}\n$1$2`,
1114
+ );
1115
+ } else {
1116
+ newCode = newCode.replace(/\/>/, ` showLabel={false}\n/>`);
1117
+ }
1093
1118
  }
1094
1119
  } else {
1095
- newCode = newCode.replace(
1096
- /legendShow\s*=\s*\{true\}/,
1097
- 'legendShow={false}',
1098
- );
1120
+ newCode = newCode.replace(/\s*showLabel\s*=\s*\{false\}/, '');
1099
1121
  }
1100
- } else {
1101
- newCode = newCode.replace(/\s*legendShow\s*=\s*\{false\}/, '');
1102
- }
1103
1122
 
1104
- // ── showGrid(默认 true,false 时注入)──
1105
- if (!result.showGrid) {
1106
- if (!/showGrid\s*=/.test(newCode)) {
1107
- if (/testId\s*=/.test(newCode)) {
1123
+ // 更新画布
1124
+ updateCode(dataSourceModal.id, newCode);
1125
+ setDataSourceModal(null);
1126
+
1127
+ // 构造 prompt 并触发 agent
1128
+ const dirLabel =
1129
+ result.direction === 'horizontal' ? '横向' : '纵向';
1130
+ const parts: string[] = [];
1131
+ if (result.dataDescription) parts.push(result.dataDescription);
1132
+ parts.push(`方向: ${dirLabel}`);
1133
+ if (!result.showGrid) parts.push('隐藏网格线');
1134
+ if (!result.showLabel) parts.push('隐藏数据标签');
1135
+ const userPrompt = parts.join('。');
1136
+ triggerDataSourceAgent(
1137
+ dataSourceModal.id,
1138
+ 'BarChart',
1139
+ newCode,
1140
+ userPrompt,
1141
+ 'canvas-sql-query',
1142
+ );
1143
+ }}
1144
+ onCancel={() => setDataSourceModal(null)}
1145
+ />
1146
+
1147
+ <LineChartDataSourceModal
1148
+ open={
1149
+ !!dataSourceModal && dataSourceModal.componentType === 'LineChart'
1150
+ }
1151
+ code={dataSourceModal?.code ?? ''}
1152
+ onConfirm={(result) => {
1153
+ if (!dataSourceModal) return;
1154
+ let newCode = result.code;
1155
+
1156
+ // ── legendShow ──
1157
+ if (!result.legendShow) {
1158
+ if (!/legendShow\s*=\s*\{/.test(newCode)) {
1159
+ if (/testId\s*=/.test(newCode)) {
1160
+ newCode = newCode.replace(
1161
+ /([ \t]*)(testId\s*=)/,
1162
+ `$1legendShow={false}\n$1$2`,
1163
+ );
1164
+ } else {
1165
+ newCode = newCode.replace(
1166
+ /\/>/,
1167
+ ` legendShow={false}\n/>`,
1168
+ );
1169
+ }
1170
+ } else {
1108
1171
  newCode = newCode.replace(
1109
- /([ \t]*)(testId\s*=)/,
1110
- `$1showGrid={false}\n$1$2`,
1172
+ /legendShow\s*=\s*\{true\}/,
1173
+ 'legendShow={false}',
1111
1174
  );
1112
- } else {
1113
- newCode = newCode.replace(/\/>/, ` showGrid={false}\n/>`);
1114
1175
  }
1176
+ } else {
1177
+ newCode = newCode.replace(/\s*legendShow\s*=\s*\{false\}/, '');
1115
1178
  }
1116
- } else {
1117
- newCode = newCode.replace(/\s*showGrid\s*=\s*\{false\}/, '');
1118
- }
1119
1179
 
1120
- // ── showAxis(默认 true,false 时注入)──
1121
- if (!result.showAxis) {
1122
- if (!/showAxis\s*=/.test(newCode)) {
1123
- if (/testId\s*=/.test(newCode)) {
1124
- newCode = newCode.replace(
1125
- /([ \t]*)(testId\s*=)/,
1126
- `$1showAxis={false}\n$1$2`,
1127
- );
1128
- } else {
1129
- newCode = newCode.replace(/\/>/, ` showAxis={false}\n/>`);
1180
+ // ── showGrid(默认 true,false 时注入)──
1181
+ if (!result.showGrid) {
1182
+ if (!/showGrid\s*=/.test(newCode)) {
1183
+ if (/testId\s*=/.test(newCode)) {
1184
+ newCode = newCode.replace(
1185
+ /([ \t]*)(testId\s*=)/,
1186
+ `$1showGrid={false}\n$1$2`,
1187
+ );
1188
+ } else {
1189
+ newCode = newCode.replace(/\/>/, ` showGrid={false}\n/>`);
1190
+ }
1130
1191
  }
1192
+ } else {
1193
+ newCode = newCode.replace(/\s*showGrid\s*=\s*\{false\}/, '');
1131
1194
  }
1132
- } else {
1133
- newCode = newCode.replace(/\s*showAxis\s*=\s*\{false\}/, '');
1134
- }
1135
1195
 
1136
- // ── showDots(默认 false,true 时注入)──
1137
- if (result.showDots) {
1138
- if (!/showDots\s*=/.test(newCode)) {
1139
- if (/testId\s*=/.test(newCode)) {
1140
- newCode = newCode.replace(
1141
- /([ \t]*)(testId\s*=)/,
1142
- `$1showDots={true}\n$1$2`,
1143
- );
1144
- } else {
1145
- newCode = newCode.replace(/\/>/, ` showDots={true}\n/>`);
1196
+ // ── showAxis(默认 true,false 时注入)──
1197
+ if (!result.showAxis) {
1198
+ if (!/showAxis\s*=/.test(newCode)) {
1199
+ if (/testId\s*=/.test(newCode)) {
1200
+ newCode = newCode.replace(
1201
+ /([ \t]*)(testId\s*=)/,
1202
+ `$1showAxis={false}\n$1$2`,
1203
+ );
1204
+ } else {
1205
+ newCode = newCode.replace(/\/>/, ` showAxis={false}\n/>`);
1206
+ }
1146
1207
  }
1208
+ } else {
1209
+ newCode = newCode.replace(/\s*showAxis\s*=\s*\{false\}/, '');
1147
1210
  }
1148
- } else {
1149
- newCode = newCode.replace(/\s*showDots\s*=\s*\{true\}/, '');
1150
- }
1151
1211
 
1152
- // ── showLabel(默认 false,true 时注入,仅 showDots 时有意义)──
1153
- if (result.showDots && result.showLabel) {
1154
- if (!/showLabel\s*=/.test(newCode)) {
1155
- if (/testId\s*=/.test(newCode)) {
1156
- newCode = newCode.replace(
1157
- /([ \t]*)(testId\s*=)/,
1158
- `$1showLabel={true}\n$1$2`,
1159
- );
1160
- } else {
1161
- newCode = newCode.replace(/\/>/, ` showLabel={true}\n/>`);
1212
+ // ── showDots(默认 false,true 时注入)──
1213
+ if (result.showDots) {
1214
+ if (!/showDots\s*=/.test(newCode)) {
1215
+ if (/testId\s*=/.test(newCode)) {
1216
+ newCode = newCode.replace(
1217
+ /([ \t]*)(testId\s*=)/,
1218
+ `$1showDots={true}\n$1$2`,
1219
+ );
1220
+ } else {
1221
+ newCode = newCode.replace(/\/>/, ` showDots={true}\n/>`);
1222
+ }
1162
1223
  }
1224
+ } else {
1225
+ newCode = newCode.replace(/\s*showDots\s*=\s*\{true\}/, '');
1163
1226
  }
1164
- } else {
1165
- newCode = newCode.replace(/\s*showLabel\s*=\s*\{true\}/, '');
1166
- }
1167
1227
 
1168
- // ── stacked(默认 false,true 时注入)──
1169
- if (result.stacked) {
1170
- if (!/stacked\s*=/.test(newCode)) {
1171
- if (/testId\s*=/.test(newCode)) {
1172
- newCode = newCode.replace(
1173
- /([ \t]*)(testId\s*=)/,
1174
- `$1stacked={true}\n$1$2`,
1175
- );
1176
- } else {
1177
- newCode = newCode.replace(/\/>/, ` stacked={true}\n/>`);
1228
+ // ── showLabel(默认 false,true 时注入,仅 showDots 时有意义)──
1229
+ if (result.showDots && result.showLabel) {
1230
+ if (!/showLabel\s*=/.test(newCode)) {
1231
+ if (/testId\s*=/.test(newCode)) {
1232
+ newCode = newCode.replace(
1233
+ /([ \t]*)(testId\s*=)/,
1234
+ `$1showLabel={true}\n$1$2`,
1235
+ );
1236
+ } else {
1237
+ newCode = newCode.replace(/\/>/, ` showLabel={true}\n/>`);
1238
+ }
1178
1239
  }
1240
+ } else {
1241
+ newCode = newCode.replace(/\s*showLabel\s*=\s*\{true\}/, '');
1179
1242
  }
1180
- } else {
1181
- newCode = newCode.replace(/\s*stacked\s*=\s*\{true\}/, '');
1182
- }
1183
1243
 
1184
- // ── area(默认 false,true 时注入)──
1185
- if (result.area) {
1186
- if (!/area\s*=/.test(newCode)) {
1187
- if (/testId\s*=/.test(newCode)) {
1188
- newCode = newCode.replace(
1189
- /([ \t]*)(testId\s*=)/,
1190
- `$1area={true}\n$1$2`,
1191
- );
1192
- } else {
1193
- newCode = newCode.replace(/\/>/, ` area={true}\n/>`);
1244
+ // ── stacked(默认 false,true 时注入)──
1245
+ if (result.stacked) {
1246
+ if (!/stacked\s*=/.test(newCode)) {
1247
+ if (/testId\s*=/.test(newCode)) {
1248
+ newCode = newCode.replace(
1249
+ /([ \t]*)(testId\s*=)/,
1250
+ `$1stacked={true}\n$1$2`,
1251
+ );
1252
+ } else {
1253
+ newCode = newCode.replace(/\/>/, ` stacked={true}\n/>`);
1254
+ }
1194
1255
  }
1256
+ } else {
1257
+ newCode = newCode.replace(/\s*stacked\s*=\s*\{true\}/, '');
1195
1258
  }
1196
- } else {
1197
- newCode = newCode.replace(/\s*area\s*=\s*\{true\}/, '');
1198
- }
1199
1259
 
1200
- // ── smooth(默认 false,true 时注入)──
1201
- if (result.smooth) {
1202
- if (!/smooth\s*=/.test(newCode)) {
1203
- if (/testId\s*=/.test(newCode)) {
1204
- newCode = newCode.replace(
1205
- /([ \t]*)(testId\s*=)/,
1206
- `$1smooth={true}\n$1$2`,
1207
- );
1208
- } else {
1209
- newCode = newCode.replace(/\/>/, ` smooth={true}\n/>`);
1260
+ // ── area(默认 false,true 时注入)──
1261
+ if (result.area) {
1262
+ if (!/area\s*=/.test(newCode)) {
1263
+ if (/testId\s*=/.test(newCode)) {
1264
+ newCode = newCode.replace(
1265
+ /([ \t]*)(testId\s*=)/,
1266
+ `$1area={true}\n$1$2`,
1267
+ );
1268
+ } else {
1269
+ newCode = newCode.replace(/\/>/, ` area={true}\n/>`);
1270
+ }
1210
1271
  }
1272
+ } else {
1273
+ newCode = newCode.replace(/\s*area\s*=\s*\{true\}/, '');
1211
1274
  }
1212
- } else {
1213
- newCode = newCode.replace(/\s*smooth\s*=\s*\{true\}/, '');
1214
- newCode = newCode.replace(/\s*smooth\b(?!\s*=)/, '');
1215
- }
1216
1275
 
1217
- // 更新画布
1218
- updateCode(dataSourceModal.id, newCode);
1219
- setDataSourceModal(null);
1220
-
1221
- // 构造 prompt 并触发 agent
1222
- const parts: string[] = [];
1223
- if (result.dataDescription) parts.push(result.dataDescription);
1224
- const flags: string[] = [];
1225
- if (result.stacked) flags.push('堆叠');
1226
- if (result.area) flags.push('面积填充');
1227
- if (result.smooth) flags.push('拟合曲线');
1228
- if (!result.showGrid) flags.push('无网格线');
1229
- if (!result.showAxis) flags.push('无坐标轴');
1230
- if (!result.legendShow) flags.push('隐藏图例');
1231
- if (result.showDots) flags.push('显示坐标点');
1232
- if (result.showDots && result.showLabel)
1233
- flags.push('显示坐标点数据');
1234
- if (flags.length) parts.push(`样式: ${flags.join(', ')}`);
1235
- const userPrompt = parts.join('。');
1236
- triggerDataSourceAgent(
1237
- dataSourceModal.id,
1238
- 'LineChart',
1239
- newCode,
1240
- userPrompt || undefined,
1241
- 'canvas-sql-query',
1242
- );
1243
- }}
1244
- onCancel={() => setDataSourceModal(null)}
1245
- />
1276
+ // ── smooth(默认 false,true 时注入)──
1277
+ if (result.smooth) {
1278
+ if (!/smooth\s*=/.test(newCode)) {
1279
+ if (/testId\s*=/.test(newCode)) {
1280
+ newCode = newCode.replace(
1281
+ /([ \t]*)(testId\s*=)/,
1282
+ `$1smooth={true}\n$1$2`,
1283
+ );
1284
+ } else {
1285
+ newCode = newCode.replace(/\/>/, ` smooth={true}\n/>`);
1286
+ }
1287
+ }
1288
+ } else {
1289
+ newCode = newCode.replace(/\s*smooth\s*=\s*\{true\}/, '');
1290
+ newCode = newCode.replace(/\s*smooth\b(?!\s*=)/, '');
1291
+ }
1246
1292
 
1247
- {/* RadarChart 修改数据来源 Modal */}
1248
- <RadarChartDataSourceModal
1249
- open={
1250
- !!dataSourceModal && dataSourceModal.componentType === 'RadarChart'
1251
- }
1252
- code={dataSourceModal?.code ?? ''}
1253
- onConfirm={({ dimensionCount, dataDescription }) => {
1254
- if (!dataSourceModal) return;
1255
- setDataSourceModal(null);
1256
-
1257
- // 不修改代码,仅构建 prompt 发给 agent
1258
- const parts: string[] = [];
1259
- parts.push(`雷达图需要${dimensionCount}个维度`);
1260
- if (dataDescription) parts.push(dataDescription);
1261
- const userPrompt = parts.join(',');
1262
- triggerDataSourceAgent(
1263
- dataSourceModal.id,
1264
- dataSourceModal.componentType,
1265
- dataSourceModal.code,
1266
- userPrompt,
1267
- 'canvas-sql-query',
1268
- );
1269
- }}
1270
- onCancel={() => setDataSourceModal(null)}
1271
- />
1293
+ // 更新画布
1294
+ updateCode(dataSourceModal.id, newCode);
1295
+ setDataSourceModal(null);
1272
1296
 
1273
- {/* MultiChart 修改数据来源 Modal */}
1274
- <MultiChartDataSourceModal
1275
- open={
1276
- !!dataSourceModal && dataSourceModal.componentType === 'MultiChart'
1277
- }
1278
- code={dataSourceModal?.code ?? ''}
1279
- onConfirm={({
1280
- dataDescription,
1281
- charts,
1282
- chartConfigs,
1283
- code: origCode,
1284
- }) => {
1285
- if (!dataSourceModal) return;
1286
- let newCode = origCode;
1287
-
1288
- // 更新 charts prop
1289
- const chartsStr = `charts={${JSON.stringify(charts)}}`;
1290
- if (/charts=\{.*?\}/s.test(newCode)) {
1291
- newCode = newCode.replace(/charts=\{.*?\}/s, chartsStr);
1292
- } else if (/testId\s*=/.test(newCode)) {
1293
- newCode = newCode.replace(
1294
- /([ \t]*)(testId\s*=)/,
1295
- `$1${chartsStr}\n$1$2`,
1297
+ // 构造 prompt 并触发 agent
1298
+ const parts: string[] = [];
1299
+ if (result.dataDescription) parts.push(result.dataDescription);
1300
+ const flags: string[] = [];
1301
+ if (result.stacked) flags.push('堆叠');
1302
+ if (result.area) flags.push('面积填充');
1303
+ if (result.smooth) flags.push('拟合曲线');
1304
+ if (!result.showGrid) flags.push('无网格线');
1305
+ if (!result.showAxis) flags.push('无坐标轴');
1306
+ if (!result.legendShow) flags.push('隐藏图例');
1307
+ if (result.showDots) flags.push('显示坐标点');
1308
+ if (result.showDots && result.showLabel)
1309
+ flags.push('显示坐标点数据');
1310
+ if (flags.length) parts.push(`样式: ${flags.join(', ')}`);
1311
+ const userPrompt = parts.join('。');
1312
+ triggerDataSourceAgent(
1313
+ dataSourceModal.id,
1314
+ 'LineChart',
1315
+ newCode,
1316
+ userPrompt || undefined,
1317
+ 'canvas-sql-query',
1296
1318
  );
1319
+ }}
1320
+ onCancel={() => setDataSourceModal(null)}
1321
+ />
1322
+
1323
+ {/* RadarChart 修改数据来源 Modal */}
1324
+ <RadarChartDataSourceModal
1325
+ open={
1326
+ !!dataSourceModal &&
1327
+ dataSourceModal.componentType === 'RadarChart'
1297
1328
  }
1329
+ code={dataSourceModal?.code ?? ''}
1330
+ onConfirm={({ dimensionCount, dataDescription }) => {
1331
+ if (!dataSourceModal) return;
1332
+ setDataSourceModal(null);
1333
+
1334
+ // 不修改代码,仅构建 prompt 发给 agent
1335
+ const parts: string[] = [];
1336
+ parts.push(`雷达图需要${dimensionCount}个维度`);
1337
+ if (dataDescription) parts.push(dataDescription);
1338
+ const userPrompt = parts.join(',');
1339
+ triggerDataSourceAgent(
1340
+ dataSourceModal.id,
1341
+ dataSourceModal.componentType,
1342
+ dataSourceModal.code,
1343
+ userPrompt,
1344
+ 'canvas-sql-query',
1345
+ );
1346
+ }}
1347
+ onCancel={() => setDataSourceModal(null)}
1348
+ />
1298
1349
 
1299
- // 更新 chartConfig prop
1300
- const configObj: Record<string, Record<string, any>> = {};
1301
- for (const [chartKey, config] of Object.entries(chartConfigs)) {
1302
- const nonDefaults: Record<string, any> = {};
1303
- for (const [k, v] of Object.entries(config)) {
1304
- // 只存非默认值的配置
1305
- if (
1306
- k === 'showGrid' ||
1307
- k === 'showLabel' ||
1308
- k === 'legendShow'
1309
- ) {
1310
- if (!v) nonDefaults[k] = false;
1311
- } else {
1312
- if (v) nonDefaults[k] = true;
1313
- }
1314
- }
1315
- if (Object.keys(nonDefaults).length > 0) {
1316
- configObj[chartKey] = nonDefaults;
1317
- }
1350
+ {/* MultiChart 修改数据来源 Modal */}
1351
+ <MultiChartDataSourceModal
1352
+ open={
1353
+ !!dataSourceModal &&
1354
+ dataSourceModal.componentType === 'MultiChart'
1318
1355
  }
1319
- if (Object.keys(configObj).length > 0) {
1320
- const configStr = `chartConfig={${JSON.stringify(configObj)}}`;
1321
- if (/chartConfig=\{[\s\S]*?\}\s*(?=\w+=|\/?>)/.test(newCode)) {
1322
- newCode = newCode.replace(
1323
- /chartConfig=\{[\s\S]*?\}\s*(?=\w+=|\/?>)/,
1324
- configStr + '\n ',
1325
- );
1356
+ code={dataSourceModal?.code ?? ''}
1357
+ onConfirm={({
1358
+ dataDescription,
1359
+ charts,
1360
+ chartConfigs,
1361
+ code: origCode,
1362
+ }) => {
1363
+ if (!dataSourceModal) return;
1364
+ let newCode = origCode;
1365
+
1366
+ // 更新 charts prop
1367
+ const chartsStr = `charts={${JSON.stringify(charts)}}`;
1368
+ if (/charts=\{.*?\}/s.test(newCode)) {
1369
+ newCode = newCode.replace(/charts=\{.*?\}/s, chartsStr);
1326
1370
  } else if (/testId\s*=/.test(newCode)) {
1327
1371
  newCode = newCode.replace(
1328
1372
  /([ \t]*)(testId\s*=)/,
1329
- `$1${configStr}\n$1$2`,
1373
+ `$1${chartsStr}\n$1$2`,
1330
1374
  );
1331
1375
  }
1332
- }
1333
1376
 
1334
- updateCode(dataSourceModal.id, newCode);
1335
- setDataSourceModal(null);
1336
-
1337
- // 构造 prompt 发给 agent
1338
- const parts: string[] = [];
1339
- if (dataDescription) parts.push(dataDescription);
1340
- parts.push(`复合图表包含:${charts.join('、')}`);
1341
- // 附加各图表配置描述(与单独图表 DataSource modal 输出一致)
1342
- for (const [chartKey, config] of Object.entries(chartConfigs)) {
1343
- const flags: string[] = [];
1344
- for (const [k, v] of Object.entries(config)) {
1345
- if (v === false || v === '' || v === undefined || v === null)
1346
- continue;
1347
- // select/slider 类型:输出具体值
1348
- if (typeof v === 'string') {
1349
- if (k === 'variant' && v === 'groupBar') flags.push('多系列');
1350
- else if (k === 'variant' && v === 'bar') flags.push('单系列');
1351
- else if (k === 'variant' && v === 'ring')
1352
- flags.push('环形图');
1353
- else if (k === 'direction' && v === 'horizontal')
1354
- flags.push('横向');
1355
- else if (k === 'direction' && v === 'vertical')
1356
- flags.push('纵向');
1357
- } else if (typeof v === 'number') {
1358
- if (k === 'dimensionCount') flags.push(`${v}个维度`);
1359
- } else if (v === true) {
1360
- // boolean 类型:输出中文描述
1361
- if (k === 'stacked') flags.push('堆叠');
1362
- else if (k === 'percent') flags.push('百分比');
1363
- else if (k === 'area') flags.push('面积填充');
1364
- else if (k === 'smooth') flags.push('拟合曲线');
1365
- else if (k === 'showDots') flags.push('显示坐标点');
1366
- else if (k === 'showLabel') flags.push('显示坐标点数据');
1367
- // 默认开启的项目只在关闭时才有意义,开启时不输出
1377
+ // 更新 chartConfig prop
1378
+ const configObj: Record<string, Record<string, any>> = {};
1379
+ for (const [chartKey, config] of Object.entries(chartConfigs)) {
1380
+ const nonDefaults: Record<string, any> = {};
1381
+ for (const [k, v] of Object.entries(config)) {
1382
+ // 只存非默认值的配置
1383
+ if (
1384
+ k === 'showGrid' ||
1385
+ k === 'showLabel' ||
1386
+ k === 'legendShow'
1387
+ ) {
1388
+ if (!v) nonDefaults[k] = false;
1389
+ } else {
1390
+ if (v) nonDefaults[k] = true;
1391
+ }
1392
+ }
1393
+ if (Object.keys(nonDefaults).length > 0) {
1394
+ configObj[chartKey] = nonDefaults;
1368
1395
  }
1369
1396
  }
1370
- // 默认开启的项目关闭时输出
1371
- if (config.showGrid === false) flags.push('隐藏网格线');
1372
- if (config.showAxis === false) flags.push('隐藏坐标轴');
1373
- if (config.legendShow === false) flags.push('隐藏图例');
1374
- if (config.showLabel === false) flags.push('隐藏数据标签');
1375
- if (flags.length > 0) {
1376
- parts.push(`${chartKey}: ${flags.join(', ')}`);
1397
+ if (Object.keys(configObj).length > 0) {
1398
+ const configStr = `chartConfig={${JSON.stringify(configObj)}}`;
1399
+ if (/chartConfig=\{[\s\S]*?\}\s*(?=\w+=|\/?>)/.test(newCode)) {
1400
+ newCode = newCode.replace(
1401
+ /chartConfig=\{[\s\S]*?\}\s*(?=\w+=|\/?>)/,
1402
+ configStr + '\n ',
1403
+ );
1404
+ } else if (/testId\s*=/.test(newCode)) {
1405
+ newCode = newCode.replace(
1406
+ /([ \t]*)(testId\s*=)/,
1407
+ `$1${configStr}\n$1$2`,
1408
+ );
1409
+ }
1377
1410
  }
1378
- }
1379
- parts.push(
1380
- '请返回 MultiChartData 格式:{ xData: string[], series: [{ name, data }] }',
1381
- );
1382
- const userPrompt = parts.join('。');
1383
- triggerDataSourceAgent(
1384
- dataSourceModal.id,
1385
- dataSourceModal.componentType,
1386
- newCode,
1387
- userPrompt,
1388
- 'canvas-sql-query',
1389
- );
1390
- }}
1391
- onCancel={() => setDataSourceModal(null)}
1392
- />
1393
1411
 
1394
- {/* MultiChart 切到 table 时的列配置 Modal(virtualCode 桥接) */}
1395
- {virtualTableConfigModal && (
1396
- <TableConfigModal
1397
- code={virtualTableConfigModal.virtualCode}
1398
- onConfirm={(newVirtualCode) => {
1399
- virtualTableConfigModal.onConfirm(newVirtualCode);
1400
- setVirtualTableConfigModal(null);
1412
+ updateCode(dataSourceModal.id, newCode);
1413
+ setDataSourceModal(null);
1414
+
1415
+ // 构造 prompt 发给 agent
1416
+ const parts: string[] = [];
1417
+ if (dataDescription) parts.push(dataDescription);
1418
+ parts.push(`复合图表包含:${charts.join('、')}`);
1419
+ // 附加各图表配置描述(与单独图表 DataSource modal 输出一致)
1420
+ for (const [chartKey, config] of Object.entries(chartConfigs)) {
1421
+ const flags: string[] = [];
1422
+ for (const [k, v] of Object.entries(config)) {
1423
+ if (v === false || v === '' || v === undefined || v === null)
1424
+ continue;
1425
+ // select/slider 类型:输出具体值
1426
+ if (typeof v === 'string') {
1427
+ if (k === 'variant' && v === 'groupBar')
1428
+ flags.push('多系列');
1429
+ else if (k === 'variant' && v === 'bar')
1430
+ flags.push('单系列');
1431
+ else if (k === 'variant' && v === 'ring')
1432
+ flags.push('环形图');
1433
+ else if (k === 'direction' && v === 'horizontal')
1434
+ flags.push('横向');
1435
+ else if (k === 'direction' && v === 'vertical')
1436
+ flags.push('纵向');
1437
+ } else if (typeof v === 'number') {
1438
+ if (k === 'dimensionCount') flags.push(`${v}个维度`);
1439
+ } else if (v === true) {
1440
+ // boolean 类型:输出中文描述
1441
+ if (k === 'stacked') flags.push('堆叠');
1442
+ else if (k === 'percent') flags.push('百分比');
1443
+ else if (k === 'area') flags.push('面积填充');
1444
+ else if (k === 'smooth') flags.push('拟合曲线');
1445
+ else if (k === 'showDots') flags.push('显示坐标点');
1446
+ else if (k === 'showLabel') flags.push('显示坐标点数据');
1447
+ // 默认开启的项目只在关闭时才有意义,开启时不输出
1448
+ }
1449
+ }
1450
+ // 默认开启的项目关闭时输出
1451
+ if (config.showGrid === false) flags.push('隐藏网格线');
1452
+ if (config.showAxis === false) flags.push('隐藏坐标轴');
1453
+ if (config.legendShow === false) flags.push('隐藏图例');
1454
+ if (config.showLabel === false) flags.push('隐藏数据标签');
1455
+ if (flags.length > 0) {
1456
+ parts.push(`${chartKey}: ${flags.join(', ')}`);
1457
+ }
1458
+ }
1459
+ parts.push(
1460
+ '请返回 MultiChartData 格式:{ xData: string[], series: [{ name, data }] }',
1461
+ );
1462
+ const userPrompt = parts.join('。');
1463
+ triggerDataSourceAgent(
1464
+ dataSourceModal.id,
1465
+ dataSourceModal.componentType,
1466
+ newCode,
1467
+ userPrompt,
1468
+ 'canvas-sql-query',
1469
+ );
1401
1470
  }}
1402
- onCancel={() => setVirtualTableConfigModal(null)}
1471
+ onCancel={() => setDataSourceModal(null)}
1403
1472
  />
1404
- )}
1405
1473
 
1406
- {/* ── 通用文本输入弹窗(如修改标题) ── */}
1407
- <PromptModal
1408
- open={!!promptModal}
1409
- label={promptModal?.label ?? ''}
1410
- placeholder={promptModal?.placeholder}
1411
- defaultValue={promptModal?.defaultValue ?? ''}
1412
- onConfirm={(value) => {
1413
- promptModal?.onConfirm(value);
1414
- setPromptModal(null);
1415
- }}
1416
- onCancel={() => setPromptModal(null)}
1417
- />
1418
- <CanvasAiModal
1419
- open={aiModalOpen}
1420
- onClose={() => setAiModalOpen(false)}
1421
- onSubmit={handleAiSubmit}
1422
- />
1474
+ {/* MultiChart 切到 table 时的列配置 Modal(virtualCode 桥接) */}
1475
+ {virtualTableConfigModal && (
1476
+ <TableConfigModal
1477
+ code={virtualTableConfigModal.virtualCode}
1478
+ onConfirm={(newVirtualCode) => {
1479
+ virtualTableConfigModal.onConfirm(newVirtualCode);
1480
+ setVirtualTableConfigModal(null);
1481
+ }}
1482
+ onCancel={() => setVirtualTableConfigModal(null)}
1483
+ />
1484
+ )}
1485
+
1486
+ {/* ── 通用文本输入弹窗(如修改标题) ── */}
1487
+ <PromptModal
1488
+ open={!!promptModal}
1489
+ label={promptModal?.label ?? ''}
1490
+ placeholder={promptModal?.placeholder}
1491
+ defaultValue={promptModal?.defaultValue ?? ''}
1492
+ onConfirm={(value) => {
1493
+ promptModal?.onConfirm(value);
1494
+ setPromptModal(null);
1495
+ }}
1496
+ onCancel={() => setPromptModal(null)}
1497
+ />
1498
+ <CanvasAiModal
1499
+ open={aiModalOpen}
1500
+ onClose={() => setAiModalOpen(false)}
1501
+ onSubmit={handleAiSubmit}
1502
+ />
1503
+ </div>
1423
1504
  </div>
1424
1505
  </div>
1425
1506
  );