bi-sdk-react 0.0.76 → 0.0.78

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,17 +1,258 @@
1
- import React, { useRef } from "react";
2
- import { Form, InputNumber, Space, Tooltip } from "antd";
1
+ import {
2
+ AlignCenterOutlined,
3
+ AlignLeftOutlined,
4
+ AlignRightOutlined,
5
+ BarChartOutlined,
6
+ DashboardOutlined,
7
+ DotChartOutlined,
8
+ FontSizeOutlined,
9
+ FullscreenOutlined,
10
+ InfoCircleOutlined,
11
+ LineChartOutlined,
12
+ PieChartOutlined,
13
+ VerticalAlignBottomOutlined,
14
+ VerticalAlignMiddleOutlined,
15
+ VerticalAlignTopOutlined,
16
+ } from "@ant-design/icons";
3
17
  import Editor from "@monaco-editor/react";
4
- import type { PropEditorProps } from "./types";
18
+ import {
19
+ Col,
20
+ Collapse,
21
+ ColorPicker,
22
+ Drawer,
23
+ Flex,
24
+ Form,
25
+ Input,
26
+ InputNumber,
27
+ Radio,
28
+ Row,
29
+ Select,
30
+ Space,
31
+ Switch,
32
+ Tooltip,
33
+ message,
34
+ } from "antd";
35
+ import * as echarts from "echarts";
36
+ import "echarts-liquidfill";
37
+ import "echarts-wordcloud";
38
+ import React, {
39
+ PropsWithChildren,
40
+ useContext,
41
+ useMemo,
42
+ useRef,
43
+ useState,
44
+ } from "react";
45
+ import * as ts from "typescript";
46
+ import { PageContext } from "../../../context/PageContext";
5
47
  import { IconFont } from "../../../icon/IconFont";
6
- import { InfoCircleOutlined } from "@ant-design/icons";
48
+ import type { PropEditorProps } from "./types";
49
+ import styled from "styled-components";
50
+
51
+ const ChartTypeRadio = styled(Radio.Group)`
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: 8px;
55
+
56
+ .ant-radio-button-wrapper {
57
+ height: unset;
58
+ width: 68px;
59
+ height: 68px;
60
+ border-radius: 4px;
61
+
62
+ .ant-radio-button-label > .ant-flex {
63
+ flex-direction: column;
64
+ align-items: center;
65
+ justify-content: center;
66
+ padding: 8px;
67
+ gap: 4px;
68
+ font-size: 12px;
69
+
70
+ .anticon {
71
+ font-size: 28px;
72
+ }
73
+ }
74
+ }
75
+ `
7
76
 
8
77
  export type EchartsModel = { script: string; height?: number };
9
78
 
79
+ const OuterRect: React.FC<
80
+ PropsWithChildren<{
81
+ width: number;
82
+ height: number;
83
+ gap?: number;
84
+ active?: "left" | "right" | "top" | "bottom" | null;
85
+ }>
86
+ > = ({ width, height, gap = 10, active, children }) => {
87
+ return (
88
+ <div
89
+ style={{
90
+ width,
91
+ height,
92
+ position: "relative",
93
+ padding: gap,
94
+ borderRadius: 6,
95
+ border: "2px solid var(--ant-color-border-secondary)",
96
+ }}
97
+ >
98
+ <div
99
+ style={{
100
+ width: "100%",
101
+ height: "100%",
102
+ borderRadius: 6,
103
+ border: "2px dotted var(--ant-color-border)",
104
+ display: "flex",
105
+ flexDirection: "column",
106
+ justifyContent: "center",
107
+ alignItems: "center",
108
+ fontSize: 10,
109
+ userSelect: "none",
110
+ }}
111
+ >
112
+ {children}
113
+ </div>
114
+ <div
115
+ style={{
116
+ position: "absolute",
117
+ top: 0,
118
+ left: width / 2 - 3,
119
+ width: 4,
120
+ height: gap,
121
+ backgroundColor:
122
+ active === "top" ? "var(--ant-color-primary)" : "#e8e8e8",
123
+ }}
124
+ ></div>
125
+ <div
126
+ style={{
127
+ position: "absolute",
128
+ bottom: 0,
129
+ left: width / 2 - 3,
130
+ width: 4,
131
+ height: gap,
132
+ backgroundColor:
133
+ active === "bottom" ? "var(--ant-color-primary)" : "#e8e8e8",
134
+ }}
135
+ ></div>
136
+ <div
137
+ style={{
138
+ position: "absolute",
139
+ top: height / 2 - 3,
140
+ left: 0,
141
+ height: 4,
142
+ width: gap,
143
+ backgroundColor:
144
+ active === "left" ? "var(--ant-color-primary)" : "#e8e8e8",
145
+ }}
146
+ ></div>
147
+ <div
148
+ style={{
149
+ position: "absolute",
150
+ top: width / 2 - 3,
151
+ right: 0,
152
+ height: 4,
153
+ width: gap,
154
+ backgroundColor:
155
+ active === "right" ? "var(--ant-color-primary)" : "#e8e8e8",
156
+ }}
157
+ ></div>
158
+ </div>
159
+ );
160
+ };
161
+
162
+ const InnerRect: React.FC<
163
+ PropsWithChildren<{
164
+ width: number;
165
+ height: number;
166
+ gap?: number;
167
+ active?: "left" | "right" | "top" | "bottom" | null;
168
+ }>
169
+ > = ({ width, height, gap = 10, active, children }) => {
170
+ return (
171
+ <div
172
+ style={{
173
+ width,
174
+ height,
175
+ position: "relative",
176
+ padding: gap,
177
+ borderRadius: 6,
178
+ border: "2px solid var(--ant-color-border-secondary)",
179
+ }}
180
+ >
181
+ <div
182
+ style={{
183
+ width: "100%",
184
+ height: "100%",
185
+ borderRadius: 6,
186
+ border: "2px dotted var(--ant-color-border)",
187
+ display: "flex",
188
+ flexDirection: "column",
189
+ justifyContent: "center",
190
+ alignItems: "center",
191
+ fontSize: 10,
192
+ userSelect: "none",
193
+ }}
194
+ >
195
+ {children}
196
+ </div>
197
+ <div
198
+ style={{
199
+ position: "absolute",
200
+ top: gap,
201
+ left: width / 2 - 3,
202
+ width: 4,
203
+ height: gap,
204
+ backgroundColor:
205
+ active === "top" ? "var(--ant-color-primary)" : "#e8e8e8",
206
+ }}
207
+ ></div>
208
+ <div
209
+ style={{
210
+ position: "absolute",
211
+ bottom: gap,
212
+ left: width / 2 - 3,
213
+ width: 4,
214
+ height: gap,
215
+ backgroundColor:
216
+ active === "bottom" ? "var(--ant-color-primary)" : "#e8e8e8",
217
+ }}
218
+ ></div>
219
+ <div
220
+ style={{
221
+ position: "absolute",
222
+ top: height / 2 - 3,
223
+ left: gap,
224
+ height: 4,
225
+ width: gap,
226
+ backgroundColor:
227
+ active === "left" ? "var(--ant-color-primary)" : "#e8e8e8",
228
+ }}
229
+ ></div>
230
+ <div
231
+ style={{
232
+ position: "absolute",
233
+ top: width / 2 - 3,
234
+ right: gap,
235
+ height: 4,
236
+ width: gap,
237
+ backgroundColor:
238
+ active === "right" ? "var(--ant-color-primary)" : "#e8e8e8",
239
+ }}
240
+ ></div>
241
+ </div>
242
+ );
243
+ };
244
+
10
245
  export const EchartsProps: React.FC<PropEditorProps<EchartsModel>> = ({
11
246
  model,
12
247
  onChange,
13
248
  }) => {
249
+ const { globalVars, env } = useContext(PageContext);
14
250
  const ref = useRef<any>(null);
251
+ const fullRef = useRef<any>(null);
252
+ const [fullScreen, setFullScreen] = useState(false);
253
+
254
+ const [selectSeries, setSelectSeries] = useState<number>(0);
255
+
15
256
  const setScript = (v?: string) =>
16
257
  onChange && onChange({ ...model, script: v || "" });
17
258
  const triggerModel = (key: keyof EchartsModel, value: any) =>
@@ -21,55 +262,1543 @@ export const EchartsProps: React.FC<PropEditorProps<EchartsModel>> = ({
21
262
  const editor = (ref.current as any)?.editor;
22
263
  editor?.getAction("editor.action.formatDocument")?.run();
23
264
  };
265
+
266
+ const fullFormat = () => {
267
+ const editor = (fullRef.current as any)?.editor;
268
+ editor?.getAction("editor.action.formatDocument")?.run();
269
+ };
270
+
271
+ /**
272
+ * 使用 AST 转换脚本:直接修改 return 对象的属性
273
+ * 将 return { a: 1 } 配合 modifyOptions { b: 2 } 转换为 return { a: 1, b: 2 }
274
+ */
275
+ const transformReturn = (modifyOptions?: Record<string, any>) => {
276
+ const code = model.script;
277
+ if (!code) {
278
+ message.warning("脚本内容为空");
279
+ return;
280
+ }
281
+
282
+ // 判断是否是来自 UI 点击的事件对象
283
+ const isEvent =
284
+ modifyOptions && (modifyOptions.nativeEvent || modifyOptions.target);
285
+ const options = isEvent ? { title: { text: "增强脚本" } } : modifyOptions;
286
+
287
+ try {
288
+ // 1. 创建 AST 源文件
289
+ const sourceFile = ts.createSourceFile(
290
+ "script.ts",
291
+ code,
292
+ ts.ScriptTarget.Latest,
293
+ true,
294
+ );
295
+
296
+ // 辅助函数:将 JS 对象转换为 AST 表达式
297
+ const convertToAST = (obj: any): ts.Expression => {
298
+ if (obj === null) return ts.factory.createNull();
299
+ if (obj === undefined) return ts.factory.createIdentifier("undefined");
300
+ if (typeof obj === "string") return ts.factory.createStringLiteral(obj);
301
+ if (typeof obj === "number")
302
+ return ts.factory.createNumericLiteral(obj);
303
+ if (typeof obj === "boolean")
304
+ return obj ? ts.factory.createTrue() : ts.factory.createFalse();
305
+ if (Array.isArray(obj)) {
306
+ return ts.factory.createArrayLiteralExpression(
307
+ obj.map((item) => convertToAST(item)),
308
+ false,
309
+ );
310
+ }
311
+ if (typeof obj === "object") {
312
+ const properties = Object.entries(obj).map(([key, value]) => {
313
+ return ts.factory.createPropertyAssignment(
314
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
315
+ ? ts.factory.createIdentifier(key)
316
+ : ts.factory.createStringLiteral(key),
317
+ convertToAST(value),
318
+ );
319
+ });
320
+ return ts.factory.createObjectLiteralExpression(properties, true);
321
+ }
322
+ return ts.factory.createNull();
323
+ };
324
+
325
+ // 辅助函数:深度合并 AST 对象字面量与 JS 对象
326
+ const mergeObjectAST = (
327
+ objectLiteral: ts.ObjectLiteralExpression,
328
+ patch: Record<string, any>,
329
+ ): ts.ObjectLiteralExpression => {
330
+ const properties = [...objectLiteral.properties];
331
+
332
+ Object.entries(patch).forEach(([key, value]) => {
333
+ const existingIndex = properties.findIndex((p) => {
334
+ if (ts.isPropertyAssignment(p)) {
335
+ if (ts.isIdentifier(p.name)) return p.name.text === key;
336
+ if (ts.isStringLiteral(p.name)) return p.name.text === key;
337
+ }
338
+ return false;
339
+ });
340
+
341
+ if (existingIndex > -1) {
342
+ const existingProp = properties[
343
+ existingIndex
344
+ ] as ts.PropertyAssignment;
345
+ // 如果新值和现有 AST 节点都是普通对象,则递归合并
346
+ if (
347
+ typeof value === "object" &&
348
+ value !== null &&
349
+ !Array.isArray(value) &&
350
+ ts.isObjectLiteralExpression(existingProp.initializer)
351
+ ) {
352
+ const mergedObj = mergeObjectAST(existingProp.initializer, value);
353
+ properties[existingIndex] = ts.factory.updatePropertyAssignment(
354
+ existingProp,
355
+ existingProp.name,
356
+ mergedObj,
357
+ );
358
+ } else if (
359
+ key === "series" &&
360
+ typeof value === "object" &&
361
+ value !== null &&
362
+ !Array.isArray(value) &&
363
+ ts.isArrayLiteralExpression(existingProp.initializer)
364
+ ) {
365
+ // 特殊处理 series:如果现有节点是数组,且补丁是对象,则更新 selectSeries 指定的项
366
+ const elements = [...existingProp.initializer.elements];
367
+ if (selectSeries < elements.length) {
368
+ const existingElement = elements[selectSeries];
369
+ if (ts.isObjectLiteralExpression(existingElement)) {
370
+ elements[selectSeries] = mergeObjectAST(
371
+ existingElement,
372
+ value as any,
373
+ );
374
+ } else {
375
+ elements[selectSeries] = convertToAST(value);
376
+ }
377
+ } else {
378
+ // 如果索引超出范围,添加新元素
379
+ elements[selectSeries] = convertToAST(value);
380
+ }
381
+ properties[existingIndex] = ts.factory.updatePropertyAssignment(
382
+ existingProp,
383
+ existingProp.name,
384
+ ts.factory.createArrayLiteralExpression(elements, true),
385
+ );
386
+ } else {
387
+ // 否则直接替换
388
+ properties[existingIndex] = ts.factory.createPropertyAssignment(
389
+ existingProp.name,
390
+ convertToAST(value),
391
+ );
392
+ }
393
+ } else {
394
+ // 添加新属性
395
+ properties.push(
396
+ ts.factory.createPropertyAssignment(
397
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
398
+ ? ts.factory.createIdentifier(key)
399
+ : ts.factory.createStringLiteral(key),
400
+ convertToAST(value),
401
+ ),
402
+ );
403
+ }
404
+ });
405
+
406
+ return ts.factory.createObjectLiteralExpression(properties, true);
407
+ };
408
+
409
+ // 2. 定义转换器:查找 return 语句并进行直接修改
410
+ const transformer = (context: ts.TransformationContext) => {
411
+ return (rootNode: ts.Node) => {
412
+ function visit(node: ts.Node): ts.Node {
413
+ // 如果是 return 语句且返回的是对象字面量
414
+ if (
415
+ ts.isReturnStatement(node) &&
416
+ node.expression &&
417
+ ts.isObjectLiteralExpression(node.expression)
418
+ ) {
419
+ const updatedObject = options
420
+ ? mergeObjectAST(node.expression, options)
421
+ : node.expression;
422
+
423
+ // 更新 return 语句,返回合并后的对象字面量
424
+ return ts.factory.updateReturnStatement(node, updatedObject);
425
+ }
426
+ return ts.visitEachChild(node, visit, context);
427
+ }
428
+ return ts.visitNode(rootNode, visit);
429
+ };
430
+ };
431
+
432
+ // 3. 执行转换
433
+ const result = ts.transform(sourceFile, [transformer]);
434
+
435
+ // 4. 将转换后的 AST 打印回代码字符串
436
+ const printer = ts.createPrinter();
437
+ let transformedCode = printer.printNode(
438
+ ts.EmitHint.Unspecified,
439
+ result.transformed[0],
440
+ sourceFile,
441
+ );
442
+
443
+ // 避免将中文转为 unicode 转义字符
444
+ transformedCode = transformedCode.replace(
445
+ /\\u([0-9a-fA-F]{4})/g,
446
+ (match, p1) => {
447
+ return String.fromCharCode(parseInt(p1, 16));
448
+ },
449
+ );
450
+
451
+ // 5. 更新编辑器内容
452
+ setScript(transformedCode);
453
+ } catch (e) {
454
+ console.error("AST Transformation Error:", e);
455
+ message.error("脚本转换失败,请检查代码语法(需返回对象字面量)");
456
+ }
457
+ };
458
+
459
+ const parseConfig: any = useMemo(() => {
460
+ try {
461
+ return JSON.parse(model.script);
462
+ } catch (jsonError) {
463
+ try {
464
+ const safeEval = new Function(
465
+ "echarts",
466
+ "console",
467
+ "Math",
468
+ "Date",
469
+ "Array",
470
+ "Object",
471
+ "String",
472
+ "Number",
473
+ "Boolean",
474
+ "$g",
475
+ "$e",
476
+ "data",
477
+ `
478
+ "use strict";
479
+ const window = undefined;
480
+ const document = undefined;
481
+ const global = undefined;
482
+ const process = undefined;
483
+ const require = undefined;
484
+ const module = undefined;
485
+ const exports = undefined;
486
+ ${model.script}
487
+ `,
488
+ );
489
+ return (safeEval as any)(
490
+ echarts,
491
+ console,
492
+ Math,
493
+ Date,
494
+ Array,
495
+ Object,
496
+ String,
497
+ Number,
498
+ Boolean,
499
+ globalVars,
500
+ env.global,
501
+ {},
502
+ );
503
+ } catch (evalError) {
504
+ // console.error("ECharts 配置解析失败:", evalError);
505
+ return null;
506
+ }
507
+ }
508
+ }, [model.script]);
509
+
510
+ const positionRender = (propName: string) => {
511
+ const [focus, setFocus] = useState<
512
+ "top" | "bottom" | "left" | "right" | null
513
+ >();
514
+
515
+ const topType = useMemo(() => {
516
+ const top = parseConfig?.[propName]?.top;
517
+ if (typeof top === "number") {
518
+ return "px";
519
+ }
520
+ if (["auto", "top", "bottom", "middle"].includes(top)) {
521
+ return top;
522
+ }
523
+ return "px";
524
+ }, [parseConfig?.[propName]?.top]);
525
+
526
+ const onTopTypeChange = (v: string) => {
527
+ if (["auto", "top", "bottom", "middle"].includes(v)) {
528
+ transformReturn({ [propName]: { top: v } });
529
+ } else if (v === "px") {
530
+ transformReturn({ [propName]: { top: undefined } });
531
+ }
532
+ };
533
+
534
+ const leftType = useMemo(() => {
535
+ const left = parseConfig?.[propName]?.left;
536
+ if (typeof left === "number") {
537
+ return "px";
538
+ }
539
+ if (["auto", "left", "right", "center"].includes(left)) {
540
+ return left;
541
+ }
542
+ return "px";
543
+ }, [parseConfig?.[propName]?.left]);
544
+
545
+ const onLeftTypeChange = (v: string) => {
546
+ if (["auto", "left", "right", "center"].includes(v)) {
547
+ transformReturn({ [propName]: { left: v } });
548
+ } else if (v === "px") {
549
+ transformReturn({ [propName]: { left: undefined } });
550
+ }
551
+ };
552
+
553
+ return (
554
+ <Flex justify="space-between" style={{ width: "100%" }} gap={12}>
555
+ <OuterRect width={106} height={106} gap={20} active={focus}>
556
+ POS
557
+ </OuterRect>
558
+ <Row gutter={[4, 4]} style={{ width: 180 }}>
559
+ <Col span={24}>
560
+ <Radio.Group
561
+ size="small"
562
+ optionType="button"
563
+ buttonStyle="solid"
564
+ block
565
+ value={topType}
566
+ onChange={(e) => onTopTypeChange(e.target.value)}
567
+ options={[
568
+ {
569
+ label: "Auto",
570
+ value: "auto",
571
+ },
572
+ {
573
+ label: "PX",
574
+ value: "px",
575
+ },
576
+ {
577
+ label: <VerticalAlignTopOutlined />,
578
+ value: "top",
579
+ },
580
+ {
581
+ label: <VerticalAlignMiddleOutlined />,
582
+ value: "middle",
583
+ },
584
+ {
585
+ label: <VerticalAlignBottomOutlined />,
586
+ value: "bottom",
587
+ },
588
+ ]}
589
+ />
590
+ </Col>
591
+ <Col span={24}>
592
+ <Radio.Group
593
+ size="small"
594
+ optionType="button"
595
+ buttonStyle="solid"
596
+ block
597
+ value={leftType}
598
+ onChange={(e) => onLeftTypeChange(e.target.value)}
599
+ options={[
600
+ {
601
+ label: "Auto",
602
+ value: "auto",
603
+ },
604
+ {
605
+ label: "PX",
606
+ value: "px",
607
+ },
608
+ {
609
+ label: <AlignLeftOutlined />,
610
+ value: "left",
611
+ },
612
+ {
613
+ label: <AlignCenterOutlined />,
614
+ value: "center",
615
+ },
616
+ {
617
+ label: <AlignRightOutlined />,
618
+ value: "right",
619
+ },
620
+ ]}
621
+ />
622
+ </Col>
623
+ <Col span={12}>
624
+ <InputNumber
625
+ value={
626
+ topType === "px" ? parseConfig?.[propName]?.top : undefined
627
+ }
628
+ min={0}
629
+ size="small"
630
+ prefix="上"
631
+ suffix="px"
632
+ onChange={(v) =>
633
+ transformReturn({
634
+ [propName]: { top: v },
635
+ })
636
+ }
637
+ onFocus={() => setFocus("top")}
638
+ onBlur={() => setFocus(null)}
639
+ disabled={topType !== "px"}
640
+ style={{ width: "100%" }}
641
+ />
642
+ </Col>
643
+ <Col span={12}>
644
+ <InputNumber
645
+ value={
646
+ leftType === "px" ? parseConfig?.[propName]?.left : undefined
647
+ }
648
+ min={0}
649
+ size="small"
650
+ prefix="左"
651
+ suffix="px"
652
+ onChange={(v) =>
653
+ transformReturn({
654
+ [propName]: { left: v },
655
+ })
656
+ }
657
+ onFocus={() => setFocus("left")}
658
+ onBlur={() => setFocus(null)}
659
+ disabled={leftType !== "px"}
660
+ style={{ width: "100%" }}
661
+ />
662
+ </Col>
663
+ <Col span={12}>
664
+ <InputNumber
665
+ value={parseConfig?.[propName]?.bottom}
666
+ min={0}
667
+ size="small"
668
+ prefix="下"
669
+ suffix="px"
670
+ onChange={(v) =>
671
+ transformReturn({
672
+ [propName]: { bottom: v },
673
+ })
674
+ }
675
+ onFocus={() => setFocus("bottom")}
676
+ onBlur={() => setFocus(null)}
677
+ style={{ width: "100%" }}
678
+ />
679
+ </Col>
680
+ <Col span={12}>
681
+ <InputNumber
682
+ value={parseConfig?.[propName]?.right}
683
+ min={0}
684
+ size="small"
685
+ prefix="右"
686
+ suffix="px"
687
+ onChange={(v) =>
688
+ transformReturn({
689
+ [propName]: { right: v },
690
+ })
691
+ }
692
+ onFocus={() => setFocus("right")}
693
+ onBlur={() => setFocus(null)}
694
+ style={{ width: "100%" }}
695
+ />
696
+ </Col>
697
+ </Row>
698
+ </Flex>
699
+ );
700
+ };
701
+
24
702
  return (
25
703
  <Form>
26
- <Form.Item
27
- label="高度"
28
- labelCol={{ span: 6 }}
29
- wrapperCol={{ span: 18 }}
30
- >
31
- <InputNumber
32
- value={model.height}
33
- size="small"
34
- min={10}
35
- max={1000}
36
- suffix="px"
37
- onChange={(v) => triggerModel("height", v)}
38
- />
39
- </Form.Item>
40
- <Form.Item
41
- layout="vertical"
42
- label={
43
- <>
44
- <Space>
45
- Echarts 渲染脚本{" "}
46
- <Tooltip
47
- title={
48
- <>
49
- 模板语法参考
50
- <ul style={{padding: 0, listStyle: "none"}}>
51
- <li>变量调用:{ `{{变量名}}` }</li>
52
- <li>环境变量:{ `{{$e.变量名}}` }</li>
53
- <li>全局变量:{ `{{$g.变量名}}` }</li>
54
- <li>函数参数:{ `{{data}}` }</li>
704
+ <Collapse
705
+ size="small"
706
+ defaultActiveKey={["base", "custom"]}
707
+ items={[
708
+ {
709
+ key: "base",
710
+ label: "基础配置",
711
+ collapsible: "icon",
712
+ children: (
713
+ <Form.Item
714
+ label="高度"
715
+ labelCol={{ span: 6 }}
716
+ wrapperCol={{ span: 18 }}
717
+ >
718
+ <InputNumber
719
+ value={model.height}
720
+ size="small"
721
+ min={10}
722
+ max={1000}
723
+ suffix="px"
724
+ onChange={(v) => triggerModel("height", v)}
725
+ />
726
+ </Form.Item>
727
+ ),
728
+ },
729
+ {
730
+ key: "title",
731
+ label: "标题",
732
+ collapsible: "icon",
733
+ extra: (
734
+ <Switch
735
+ size="small"
736
+ value={
737
+ typeof parseConfig?.title?.show === "boolean"
738
+ ? parseConfig.title.show
739
+ : true
740
+ }
741
+ onChange={(v) => transformReturn({ title: { show: v } })}
742
+ />
743
+ ),
744
+ children: (
745
+ <Space vertical>
746
+ <Space.Compact size="small" style={{ width: "100%" }}>
747
+ <Input
748
+ size="small"
749
+ value={parseConfig?.title?.text || ""}
750
+ prefix={
751
+ <label
752
+ style={{
753
+ color: "var(--ant-color-text-description)",
754
+ userSelect: "none",
755
+ fontSize: 10,
756
+ }}
757
+ >
758
+
759
+ </label>
760
+ }
761
+ placeholder="请输入标题"
762
+ onChange={(e) =>
763
+ transformReturn({ title: { text: e.target.value } })
764
+ }
765
+ />
766
+ <InputNumber
767
+ size="small"
768
+ value={parseConfig?.title?.textStyle?.fontSize}
769
+ min={10}
770
+ max={100}
771
+ prefix={
772
+ <FontSizeOutlined
773
+ style={{ color: "var(--ant-color-text-description)" }}
774
+ />
775
+ }
776
+ suffix="px"
777
+ onChange={(v) =>
778
+ transformReturn({
779
+ title: { textStyle: { fontSize: v } },
780
+ })
781
+ }
782
+ style={{ width: 200 }}
783
+ />
784
+ <ColorPicker
785
+ size="small"
786
+ value={parseConfig?.title?.textStyle?.color}
787
+ onChange={(v) =>
788
+ transformReturn({
789
+ title: { textStyle: { color: v.toHexString() } },
790
+ })
791
+ }
792
+ />
793
+ </Space.Compact>
794
+ <Space.Compact size="small" style={{ width: "100%" }}>
795
+ <Input
796
+ size="small"
797
+ value={parseConfig?.title?.subtext || ""}
798
+ prefix={
799
+ <label
800
+ style={{
801
+ color: "var(--ant-color-text-description)",
802
+ userSelect: "none",
803
+ fontSize: 10,
804
+ }}
805
+ >
806
+
807
+ </label>
808
+ }
809
+ placeholder="请输入副标题"
810
+ onChange={(e) =>
811
+ transformReturn({ title: { subtext: e.target.value } })
812
+ }
813
+ />
814
+ <InputNumber
815
+ size="small"
816
+ value={parseConfig?.title?.subtextStyle?.fontSize}
817
+ min={10}
818
+ max={100}
819
+ prefix={
820
+ <FontSizeOutlined
821
+ style={{ color: "var(--ant-color-text-description)" }}
822
+ />
823
+ }
824
+ suffix="px"
825
+ onChange={(v) =>
826
+ transformReturn({
827
+ title: { subtextStyle: { fontSize: v } },
828
+ })
829
+ }
830
+ style={{ width: 200 }}
831
+ />
832
+ <ColorPicker
833
+ size="small"
834
+ value={parseConfig?.title?.subtextStyle?.color}
835
+ onChange={(v) =>
836
+ transformReturn({
837
+ title: { subtextStyle: { color: v.toHexString() } },
838
+ })
839
+ }
840
+ />
841
+ </Space.Compact>
842
+ {positionRender("title")}
843
+ </Space>
844
+ ),
845
+ },
846
+ {
847
+ key: "legend",
848
+ label: "图例",
849
+ collapsible: "icon",
850
+ extra: (
851
+ <Switch
852
+ size="small"
853
+ value={
854
+ typeof parseConfig?.legend?.show === "boolean"
855
+ ? parseConfig.legend.show
856
+ : true
857
+ }
858
+ onChange={(v) => transformReturn({ legend: { show: v } })}
859
+ />
860
+ ),
861
+ children: (
862
+ <Space vertical>
863
+ <Form.Item
864
+ label="图例方向"
865
+ labelCol={{ span: 6 }}
866
+ wrapperCol={{ span: 18 }}
867
+ >
868
+ <Radio.Group
869
+ size="small"
870
+ optionType="button"
871
+ buttonStyle="solid"
872
+ value={parseConfig?.legend?.orient || "horizontal"}
873
+ onChange={(e) =>
874
+ transformReturn({ legend: { orient: e.target.value } })
875
+ }
876
+ options={[
877
+ { label: "水平", value: "horizontal" },
878
+ { label: "垂直", value: "vertical" },
879
+ ]}
880
+ />
881
+ </Form.Item>
882
+ <Form.Item
883
+ label="图例标记和文本的对齐"
884
+ labelCol={{ span: 12 }}
885
+ wrapperCol={{ span: 12 }}
886
+ >
887
+ <Radio.Group
888
+ size="small"
889
+ optionType="button"
890
+ buttonStyle="solid"
891
+ value={parseConfig?.legend?.align || "auto"}
892
+ onChange={(e) =>
893
+ transformReturn({ legend: { align: e.target.value } })
894
+ }
895
+ options={[
896
+ { label: "自动", value: "auto" },
897
+ { label: "左侧", value: "left" },
898
+ { label: "右侧", value: "right" },
899
+ ]}
900
+ />
901
+ </Form.Item>
902
+ <Form.Item
903
+ label="项间隔"
904
+ labelCol={{ span: 12 }}
905
+ wrapperCol={{ span: 12 }}
906
+ >
907
+ <InputNumber
908
+ size="small"
909
+ value={parseConfig?.legend?.itemGap}
910
+ min={0}
911
+ suffix="px"
912
+ onChange={(v) =>
913
+ transformReturn({ legend: { itemGap: v } })
914
+ }
915
+ />
916
+ </Form.Item>
917
+ <Form.Item
918
+ label="图形宽度"
919
+ labelCol={{ span: 12 }}
920
+ wrapperCol={{ span: 12 }}
921
+ >
922
+ <InputNumber
923
+ size="small"
924
+ value={parseConfig?.legend?.itemWidth}
925
+ min={0}
926
+ suffix="px"
927
+ onChange={(v) =>
928
+ transformReturn({ legend: { itemWidth: v } })
929
+ }
930
+ />
931
+ </Form.Item>
932
+ <Form.Item
933
+ label="图形高度"
934
+ labelCol={{ span: 12 }}
935
+ wrapperCol={{ span: 12 }}
936
+ >
937
+ <InputNumber
938
+ size="small"
939
+ value={parseConfig?.legend?.itemHeight}
940
+ min={0}
941
+ suffix="px"
942
+ onChange={(v) =>
943
+ transformReturn({ legend: { itemHeight: v } })
944
+ }
945
+ />
946
+ </Form.Item>
947
+ <Form.Item
948
+ label="内容格式器"
949
+ layout="vertical"
950
+ tooltip="使用字符串模板,模板变量为图例名称 {name}"
951
+ >
952
+ <Input.TextArea
953
+ value={parseConfig?.legend?.formatter}
954
+ onChange={(e) =>
955
+ transformReturn({ legend: { formatter: e.target.value } })
956
+ }
957
+ />
958
+ </Form.Item>
959
+ {positionRender("legend")}
960
+ </Space>
961
+ ),
962
+ },
963
+ {
964
+ key: "grid",
965
+ label: "直角坐标系",
966
+ collapsible: "icon",
967
+ extra: (
968
+ <Switch
969
+ size="small"
970
+ value={!!parseConfig?.grid?.show}
971
+ onChange={(v) => transformReturn({ grid: { show: v } })}
972
+ />
973
+ ),
974
+ children: (
975
+ <Space vertical>
976
+ <Form.Item
977
+ label="外边界策略"
978
+ labelCol={{ span: 6 }}
979
+ wrapperCol={{ span: 18 }}
980
+ >
981
+ <Radio.Group
982
+ size="small"
983
+ optionType="button"
984
+ buttonStyle="solid"
985
+ value={parseConfig?.grid?.outerBoundsMode || "auto"}
986
+ onChange={(e) =>
987
+ transformReturn({
988
+ grid: { outerBoundsMode: e.target.value },
989
+ })
990
+ }
991
+ options={[
992
+ { label: "auto", value: "auto" },
993
+ { label: "none", value: "none" },
994
+ { label: "same", value: "same" },
995
+ ]}
996
+ />
997
+ </Form.Item>
998
+ <Form.Item
999
+ label="背景色"
1000
+ labelCol={{ span: 12 }}
1001
+ wrapperCol={{ span: 12 }}
1002
+ >
1003
+ <ColorPicker
1004
+ size="small"
1005
+ value={parseConfig?.grid?.backgroundColor || "auto"}
1006
+ onChange={(e) =>
1007
+ transformReturn({
1008
+ grid: { backgroundColor: e.toHexString() },
1009
+ })
1010
+ }
1011
+ />
1012
+ </Form.Item>
1013
+ <Form.Item
1014
+ label="边框颜色"
1015
+ labelCol={{ span: 12 }}
1016
+ wrapperCol={{ span: 12 }}
1017
+ >
1018
+ <ColorPicker
1019
+ size="small"
1020
+ value={parseConfig?.grid?.borderColor || "auto"}
1021
+ onChange={(e) =>
1022
+ transformReturn({
1023
+ grid: { borderColor: e.toHexString() },
1024
+ })
1025
+ }
1026
+ />
1027
+ </Form.Item>
1028
+ <Form.Item
1029
+ label="边框宽度"
1030
+ labelCol={{ span: 12 }}
1031
+ wrapperCol={{ span: 12 }}
1032
+ >
1033
+ <InputNumber
1034
+ size="small"
1035
+ value={parseConfig?.grid?.borderWidth}
1036
+ min={0}
1037
+ suffix="px"
1038
+ onChange={(v) =>
1039
+ transformReturn({ grid: { borderWidth: v } })
1040
+ }
1041
+ />
1042
+ </Form.Item>
1043
+ {positionRender("grid")}
1044
+ </Space>
1045
+ ),
1046
+ },
1047
+ {
1048
+ key: "xAxis",
1049
+ label: "X坐标系",
1050
+ collapsible: "icon",
1051
+ extra: (
1052
+ <Switch
1053
+ size="small"
1054
+ value={
1055
+ typeof parseConfig?.xAxis?.show === "boolean"
1056
+ ? parseConfig.xAxis.show
1057
+ : true
1058
+ }
1059
+ onChange={(v) => transformReturn({ xAxis: { show: v } })}
1060
+ />
1061
+ ),
1062
+ children: (
1063
+ <Space vertical style={{ width: "100%" }}>
1064
+ <Form.Item
1065
+ label="坐标轴名称"
1066
+ labelCol={{ span: 12 }}
1067
+ wrapperCol={{ span: 12 }}
1068
+ >
1069
+ <Input
1070
+ size="small"
1071
+ value={parseConfig?.xAxis?.name || ""}
1072
+ onChange={(e) =>
1073
+ transformReturn({ xAxis: { name: e.target.value } })
1074
+ }
1075
+ />
1076
+ </Form.Item>
1077
+ <Form.Item
1078
+ label="位置"
1079
+ labelCol={{ span: 6 }}
1080
+ wrapperCol={{ span: 18 }}
1081
+ >
1082
+ <Radio.Group
1083
+ size="small"
1084
+ optionType="button"
1085
+ buttonStyle="solid"
1086
+ value={parseConfig?.xAxis?.position || "auto"}
1087
+ onChange={(e) =>
1088
+ transformReturn({ xAxis: { position: e.target.value } })
1089
+ }
1090
+ options={[
1091
+ { label: "top", value: "top" },
1092
+ { label: "bottom", value: "bottom" },
1093
+ ]}
1094
+ />
1095
+ </Form.Item>
1096
+ <Form.Item
1097
+ label="类型"
1098
+ labelCol={{ span: 4 }}
1099
+ wrapperCol={{ span: 20 }}
1100
+ >
1101
+ <Radio.Group
1102
+ size="small"
1103
+ optionType="button"
1104
+ buttonStyle="solid"
1105
+ value={parseConfig?.xAxis?.type || "category"}
1106
+ onChange={(e) =>
1107
+ transformReturn({ xAxis: { type: e.target.value } })
1108
+ }
1109
+ options={[
1110
+ { label: "类目轴", value: "category" },
1111
+ { label: "数值轴", value: "value" },
1112
+ { label: "时间轴", value: "time" },
1113
+ { label: "对数轴", value: "log" },
1114
+ ]}
1115
+ />
1116
+ </Form.Item>
1117
+ <Form.Item
1118
+ label="偏移量"
1119
+ labelCol={{ span: 12 }}
1120
+ wrapperCol={{ span: 12 }}
1121
+ >
1122
+ <InputNumber
1123
+ size="small"
1124
+ value={parseConfig?.xAxis?.offset || 0}
1125
+ min={0}
1126
+ suffix="px"
1127
+ onChange={(v) => transformReturn({ xAxis: { offset: v } })}
1128
+ />
1129
+ </Form.Item>
1130
+ <Form.Item
1131
+ label="名称与轴线之间的距离"
1132
+ labelCol={{ span: 12 }}
1133
+ wrapperCol={{ span: 12 }}
1134
+ >
1135
+ <InputNumber
1136
+ size="small"
1137
+ value={parseConfig?.xAxis?.nameGap || 0}
1138
+ min={0}
1139
+ suffix="px"
1140
+ onChange={(v) => transformReturn({ xAxis: { nameGap: v } })}
1141
+ />
1142
+ </Form.Item>
1143
+ <Form.Item
1144
+ label="坐标轴名字角度值"
1145
+ labelCol={{ span: 12 }}
1146
+ wrapperCol={{ span: 12 }}
1147
+ >
1148
+ <InputNumber
1149
+ size="small"
1150
+ value={parseConfig?.xAxis?.nameRotate || 0}
1151
+ min={0}
1152
+ onChange={(v) =>
1153
+ transformReturn({ xAxis: { nameRotate: v } })
1154
+ }
1155
+ />
1156
+ </Form.Item>
1157
+ <Form.Item
1158
+ label="刻度最小值"
1159
+ labelCol={{ span: 12 }}
1160
+ wrapperCol={{ span: 12 }}
1161
+ >
1162
+ <InputNumber
1163
+ size="small"
1164
+ value={parseConfig?.xAxis?.min || 0}
1165
+ min={0}
1166
+ onChange={(v) => transformReturn({ xAxis: { min: v } })}
1167
+ />
1168
+ </Form.Item>
1169
+ <Form.Item
1170
+ label="刻度最大值"
1171
+ labelCol={{ span: 12 }}
1172
+ wrapperCol={{ span: 12 }}
1173
+ >
1174
+ <InputNumber
1175
+ size="small"
1176
+ value={parseConfig?.xAxis?.max || 0}
1177
+ min={0}
1178
+ onChange={(v) => transformReturn({ xAxis: { max: v } })}
1179
+ />
1180
+ </Form.Item>
1181
+ <Form.Item
1182
+ label="分割段数"
1183
+ labelCol={{ span: 12 }}
1184
+ wrapperCol={{ span: 12 }}
1185
+ >
1186
+ <InputNumber
1187
+ size="small"
1188
+ value={parseConfig?.xAxis?.splitNumber || 5}
1189
+ min={0}
1190
+ onChange={(v) =>
1191
+ transformReturn({ xAxis: { splitNumber: v } })
1192
+ }
1193
+ />
1194
+ </Form.Item>
1195
+ <Form.Item
1196
+ label="起始值"
1197
+ labelCol={{ span: 12 }}
1198
+ wrapperCol={{ span: 12 }}
1199
+ >
1200
+ <InputNumber
1201
+ size="small"
1202
+ value={parseConfig?.xAxis?.startValue}
1203
+ min={0}
1204
+ onChange={(v) =>
1205
+ transformReturn({ xAxis: { startValue: v } })
1206
+ }
1207
+ />
1208
+ </Form.Item>
1209
+ </Space>
1210
+ ),
1211
+ },
1212
+ {
1213
+ key: "yAxis",
1214
+ label: "Y坐标系",
1215
+ collapsible: "icon",
1216
+ extra: (
1217
+ <Switch
1218
+ size="small"
1219
+ value={
1220
+ typeof parseConfig?.yAxis?.show === "boolean"
1221
+ ? parseConfig.yAxis.show
1222
+ : true
1223
+ }
1224
+ onChange={(v) => transformReturn({ yAxis: { show: v } })}
1225
+ />
1226
+ ),
1227
+ children: (
1228
+ <Space vertical style={{ width: "100%" }}>
1229
+ <Form.Item
1230
+ label="坐标轴名称"
1231
+ labelCol={{ span: 12 }}
1232
+ wrapperCol={{ span: 12 }}
1233
+ >
1234
+ <Input
1235
+ size="small"
1236
+ value={parseConfig?.yAxis?.name || ""}
1237
+ onChange={(e) =>
1238
+ transformReturn({ yAxis: { name: e.target.value } })
1239
+ }
1240
+ />
1241
+ </Form.Item>
1242
+ <Form.Item
1243
+ label="位置"
1244
+ labelCol={{ span: 6 }}
1245
+ wrapperCol={{ span: 18 }}
1246
+ >
1247
+ <Radio.Group
1248
+ size="small"
1249
+ optionType="button"
1250
+ buttonStyle="solid"
1251
+ value={parseConfig?.yAxis?.position || "auto"}
1252
+ onChange={(e) =>
1253
+ transformReturn({ yAxis: { position: e.target.value } })
1254
+ }
1255
+ options={[
1256
+ { label: "left", value: "left" },
1257
+ { label: "right", value: "right" },
1258
+ ]}
1259
+ />
1260
+ </Form.Item>
1261
+ <Form.Item
1262
+ label="类型"
1263
+ labelCol={{ span: 4 }}
1264
+ wrapperCol={{ span: 20 }}
1265
+ >
1266
+ <Radio.Group
1267
+ size="small"
1268
+ optionType="button"
1269
+ buttonStyle="solid"
1270
+ value={parseConfig?.yAxis?.type || "category"}
1271
+ onChange={(e) =>
1272
+ transformReturn({ yAxis: { type: e.target.value } })
1273
+ }
1274
+ options={[
1275
+ { label: "类目轴", value: "category" },
1276
+ { label: "数值轴", value: "value" },
1277
+ { label: "时间轴", value: "time" },
1278
+ { label: "对数轴", value: "log" },
1279
+ ]}
1280
+ />
1281
+ </Form.Item>
1282
+ <Form.Item
1283
+ label="偏移量"
1284
+ labelCol={{ span: 12 }}
1285
+ wrapperCol={{ span: 12 }}
1286
+ >
1287
+ <InputNumber
1288
+ size="small"
1289
+ value={parseConfig?.yAxis?.offset || 0}
1290
+ min={0}
1291
+ suffix="px"
1292
+ onChange={(v) => transformReturn({ yAxis: { offset: v } })}
1293
+ />
1294
+ </Form.Item>
1295
+ <Form.Item
1296
+ label="名称与轴线之间的距离"
1297
+ labelCol={{ span: 12 }}
1298
+ wrapperCol={{ span: 12 }}
1299
+ >
1300
+ <InputNumber
1301
+ size="small"
1302
+ value={parseConfig?.yAxis?.nameGap || 0}
1303
+ min={0}
1304
+ suffix="px"
1305
+ onChange={(v) => transformReturn({ yAxis: { nameGap: v } })}
1306
+ />
1307
+ </Form.Item>
1308
+ <Form.Item
1309
+ label="坐标轴名字角度值"
1310
+ labelCol={{ span: 12 }}
1311
+ wrapperCol={{ span: 12 }}
1312
+ >
1313
+ <InputNumber
1314
+ size="small"
1315
+ value={parseConfig?.yAxis?.nameRotate || 0}
1316
+ min={0}
1317
+ onChange={(v) =>
1318
+ transformReturn({ yAxis: { nameRotate: v } })
1319
+ }
1320
+ />
1321
+ </Form.Item>
1322
+ <Form.Item
1323
+ label="刻度最小值"
1324
+ labelCol={{ span: 12 }}
1325
+ wrapperCol={{ span: 12 }}
1326
+ >
1327
+ <InputNumber
1328
+ size="small"
1329
+ value={parseConfig?.yAxis?.min || 0}
1330
+ min={0}
1331
+ onChange={(v) => transformReturn({ yAxis: { min: v } })}
1332
+ />
1333
+ </Form.Item>
1334
+ <Form.Item
1335
+ label="刻度最大值"
1336
+ labelCol={{ span: 12 }}
1337
+ wrapperCol={{ span: 12 }}
1338
+ >
1339
+ <InputNumber
1340
+ size="small"
1341
+ value={parseConfig?.yAxis?.max || 0}
1342
+ min={0}
1343
+ onChange={(v) => transformReturn({ yAxis: { max: v } })}
1344
+ />
1345
+ </Form.Item>
1346
+ <Form.Item
1347
+ label="分割段数"
1348
+ labelCol={{ span: 12 }}
1349
+ wrapperCol={{ span: 12 }}
1350
+ >
1351
+ <InputNumber
1352
+ size="small"
1353
+ value={parseConfig?.yAxis?.splitNumber || 5}
1354
+ min={0}
1355
+ onChange={(v) =>
1356
+ transformReturn({ yAxis: { splitNumber: v } })
1357
+ }
1358
+ />
1359
+ </Form.Item>
1360
+ <Form.Item
1361
+ label="起始值"
1362
+ labelCol={{ span: 12 }}
1363
+ wrapperCol={{ span: 12 }}
1364
+ >
1365
+ <InputNumber
1366
+ size="small"
1367
+ value={parseConfig?.yAxis?.startValue}
1368
+ min={0}
1369
+ onChange={(v) =>
1370
+ transformReturn({ yAxis: { startValue: v } })
1371
+ }
1372
+ />
1373
+ </Form.Item>
1374
+ </Space>
1375
+ ),
1376
+ },
1377
+ {
1378
+ key: "tooltip",
1379
+ label: "提示框",
1380
+ collapsible: "icon",
1381
+ extra: (
1382
+ <Switch
1383
+ size="small"
1384
+ value={
1385
+ typeof parseConfig?.tooltip?.show === "boolean"
1386
+ ? parseConfig.tooltip.show
1387
+ : true
1388
+ }
1389
+ onChange={(v) => transformReturn({ tooltip: { show: v } })}
1390
+ />
1391
+ ),
1392
+ children: (
1393
+ <Space vertical style={{ width: "100%" }}>
1394
+ <Form.Item
1395
+ label="触发类型"
1396
+ labelCol={{ span: 6 }}
1397
+ wrapperCol={{ span: 18 }}
1398
+ >
1399
+ <Radio.Group
1400
+ size="small"
1401
+ optionType="button"
1402
+ buttonStyle="solid"
1403
+ value={parseConfig?.tooltip?.trigger || "item"}
1404
+ onChange={(e) =>
1405
+ transformReturn({ tooltip: { trigger: e.target.value } })
1406
+ }
1407
+ options={[
1408
+ { label: "item", value: "item" },
1409
+ { label: "axis", value: "axis" },
1410
+ { label: "none", value: "none" },
1411
+ ]}
1412
+ />
1413
+ </Form.Item>
1414
+ <Form.Item
1415
+ label="触发条件"
1416
+ labelCol={{ span: 6 }}
1417
+ wrapperCol={{ span: 18 }}
1418
+ >
1419
+ <Radio.Group
1420
+ size="small"
1421
+ optionType="button"
1422
+ buttonStyle="solid"
1423
+ value={parseConfig?.tooltip?.triggerOn || "item"}
1424
+ onChange={(e) =>
1425
+ transformReturn({
1426
+ tooltip: { triggerOn: e.target.value },
1427
+ })
1428
+ }
1429
+ options={[
1430
+ { label: "移动", value: "mousemove" },
1431
+ { label: "点击", value: "click" },
1432
+ { label: "移动或点击", value: "mousemove|click" },
1433
+ { label: "无", value: "none" },
1434
+ ]}
1435
+ />
1436
+ </Form.Item>
1437
+ <Form.Item
1438
+ label="背景颜色"
1439
+ labelCol={{ span: 12 }}
1440
+ wrapperCol={{ span: 12 }}
1441
+ >
1442
+ <ColorPicker
1443
+ size="small"
1444
+ value={parseConfig?.tooltip?.backgroundColor}
1445
+ onChange={(v) =>
1446
+ transformReturn({
1447
+ tooltip: { backgroundColor: v.toHexString() },
1448
+ })
1449
+ }
1450
+ />
1451
+ </Form.Item>
1452
+ <Form.Item
1453
+ label="边框颜色"
1454
+ labelCol={{ span: 12 }}
1455
+ wrapperCol={{ span: 12 }}
1456
+ >
1457
+ <ColorPicker
1458
+ size="small"
1459
+ value={parseConfig?.tooltip?.borderColor}
1460
+ onChange={(v) =>
1461
+ transformReturn({
1462
+ tooltip: { borderColor: v.toHexString() },
1463
+ })
1464
+ }
1465
+ />
1466
+ </Form.Item>
1467
+ <Form.Item
1468
+ label="边框宽度"
1469
+ labelCol={{ span: 12 }}
1470
+ wrapperCol={{ span: 12 }}
1471
+ >
1472
+ <InputNumber
1473
+ size="small"
1474
+ value={parseConfig?.tooltip?.borderWidth || 0}
1475
+ min={0}
1476
+ suffix="px"
1477
+ onChange={(v) =>
1478
+ transformReturn({ tooltip: { borderWidth: v } })
1479
+ }
1480
+ />
1481
+ </Form.Item>
1482
+ <Form.Item
1483
+ label="浮层内边距"
1484
+ labelCol={{ span: 12 }}
1485
+ wrapperCol={{ span: 12 }}
1486
+ >
1487
+ <InputNumber
1488
+ size="small"
1489
+ value={parseConfig?.tooltip?.padding || 5}
1490
+ min={0}
1491
+ suffix="px"
1492
+ onChange={(v) =>
1493
+ transformReturn({ tooltip: { padding: v } })
1494
+ }
1495
+ />
1496
+ </Form.Item>
1497
+ <Form.Item
1498
+ label="内容格式器"
1499
+ layout="vertical"
1500
+ tooltip={
1501
+ <div>
1502
+ {`模板变量有 {a}, {b},{c},{d},{e},分别表示系列名,数据名,数据值等。 在 trigger 为 'axis' 的时候,会有多个系列的数据,此时可以通过 {a0}, {a1}, {a2} 这种后面加索引的方式表示系列的索引。 不同图表类型下的 {a},{b},{c},{d} 含义不一样。 其中变量{a}, {b}, {c}, {d}在不同图表类型下代表数据含义为:`}
1503
+ <ul>
1504
+ <li>{`折线(区域)图、柱状(条形)图、K线图 : {a}(系列名称),{b}(类目值),{c}(数值), {d}(无)`}</li>
1505
+ <li>{`散点图(气泡)图 : {a}(系列名称),{b}(数据名称),{c}(数值数组), {d}(无)`}</li>
1506
+ <li>{`地图 : {a}(系列名称),{b}(区域名称),{c}(合并数值), {d}(无)`}</li>
1507
+ <li>{`饼图、仪表盘、漏斗图: {a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比)`}</li>
55
1508
  </ul>
56
- </>
1509
+ </div>
57
1510
  }
58
1511
  >
59
- <InfoCircleOutlined />
60
- </Tooltip>
1512
+ <Input.TextArea
1513
+ value={parseConfig?.tooltip?.formatter}
1514
+ onChange={(e) =>
1515
+ transformReturn({
1516
+ tooltip: { formatter: e.target.value },
1517
+ })
1518
+ }
1519
+ />
1520
+ </Form.Item>
61
1521
  </Space>
62
- <a onClick={format}>
63
- <IconFont type="icon-formate" /> 格式化
64
- </a>
65
- </>
1522
+ ),
1523
+ },
1524
+ // {
1525
+ // key: "series",
1526
+ // label: "系列",
1527
+ // collapsible: "icon",
1528
+ // extra: (
1529
+ // <Select
1530
+ // size="small"
1531
+ // options={(parseConfig?.series || []).map(
1532
+ // (item: any, index: number) => ({
1533
+ // label: item.name || `系列 ${index + 1}`,
1534
+ // value: index,
1535
+ // }),
1536
+ // )}
1537
+ // value={selectSeries}
1538
+ // onChange={(v) => setSelectSeries(v)}
1539
+ // style={{ width: 120 }}
1540
+ // />
1541
+ // ),
1542
+ // children: (
1543
+ // <Space vertical style={{ width: "100%" }}>
1544
+ // <ChartTypeRadio
1545
+ // size="small"
1546
+ // optionType="button"
1547
+ // buttonStyle="solid"
1548
+ // value={parseConfig?.series[selectSeries]?.type || "line"}
1549
+ // onChange={(e) =>
1550
+ // transformReturn({ series: { type: e.target.value } })
1551
+ // }
1552
+ // options={[
1553
+ // { label: <Flex><IconFont type="icon-chart-line" />折线图</Flex>, value: "line" },
1554
+ // { label: <Flex><IconFont type="icon-chart-bar" />柱状图</Flex>, value: "bar" },
1555
+ // { label: <Flex><IconFont type="icon-chart-candlestick" />K线图</Flex>, value: "candlestick" },
1556
+ // { label: <Flex><IconFont type="icon-chart-scatter" />散点图</Flex>, value: "scatter" },
1557
+ // { label: <Flex><IconFont type="icon-map" />地图</Flex>, value: "map" },
1558
+ // { label: <Flex><IconFont type="icon-chart-pie" />饼图</Flex>, value: "pie" },
1559
+ // { label: <Flex><IconFont type="icon-chart-gauge" />仪表盘</Flex>, value: "gauge" },
1560
+ // { label: <Flex><IconFont type="icon-chart-funnel" />漏斗图</Flex>, value: "funnel" },
1561
+ // { label: <Flex><IconFont type="icon-chart-radar" />雷达图</Flex>, value: "radar" },
1562
+ // ]}
1563
+ // />
1564
+ // <Form.Item
1565
+ // label="触发类型"
1566
+ // labelCol={{ span: 6 }}
1567
+ // wrapperCol={{ span: 18 }}
1568
+ // >
1569
+ // <Radio.Group
1570
+ // size="small"
1571
+ // optionType="button"
1572
+ // buttonStyle="solid"
1573
+ // value={parseConfig?.tooltip?.trigger || "item"}
1574
+ // onChange={(e) =>
1575
+ // transformReturn({ tooltip: { trigger: e.target.value } })
1576
+ // }
1577
+ // options={[
1578
+ // { label: "item", value: "item" },
1579
+ // { label: "axis", value: "axis" },
1580
+ // { label: "none", value: "none" },
1581
+ // ]}
1582
+ // />
1583
+ // </Form.Item>
1584
+ // <Form.Item
1585
+ // label="触发条件"
1586
+ // labelCol={{ span: 6 }}
1587
+ // wrapperCol={{ span: 18 }}
1588
+ // >
1589
+ // <Radio.Group
1590
+ // size="small"
1591
+ // optionType="button"
1592
+ // buttonStyle="solid"
1593
+ // value={parseConfig?.tooltip?.triggerOn || "item"}
1594
+ // onChange={(e) =>
1595
+ // transformReturn({
1596
+ // tooltip: { triggerOn: e.target.value },
1597
+ // })
1598
+ // }
1599
+ // options={[
1600
+ // { label: "移动", value: "mousemove" },
1601
+ // { label: "点击", value: "click" },
1602
+ // { label: "移动或点击", value: "mousemove|click" },
1603
+ // { label: "无", value: "none" },
1604
+ // ]}
1605
+ // />
1606
+ // </Form.Item>
1607
+ // <Form.Item
1608
+ // label="背景颜色"
1609
+ // labelCol={{ span: 12 }}
1610
+ // wrapperCol={{ span: 12 }}
1611
+ // >
1612
+ // <ColorPicker
1613
+ // size="small"
1614
+ // value={parseConfig?.tooltip?.backgroundColor}
1615
+ // onChange={(v) =>
1616
+ // transformReturn({
1617
+ // tooltip: { backgroundColor: v.toHexString() },
1618
+ // })
1619
+ // }
1620
+ // />
1621
+ // </Form.Item>
1622
+ // <Form.Item
1623
+ // label="边框颜色"
1624
+ // labelCol={{ span: 12 }}
1625
+ // wrapperCol={{ span: 12 }}
1626
+ // >
1627
+ // <ColorPicker
1628
+ // size="small"
1629
+ // value={parseConfig?.tooltip?.borderColor}
1630
+ // onChange={(v) =>
1631
+ // transformReturn({
1632
+ // tooltip: { borderColor: v.toHexString() },
1633
+ // })
1634
+ // }
1635
+ // />
1636
+ // </Form.Item>
1637
+ // <Form.Item
1638
+ // label="边框宽度"
1639
+ // labelCol={{ span: 12 }}
1640
+ // wrapperCol={{ span: 12 }}
1641
+ // >
1642
+ // <InputNumber
1643
+ // size="small"
1644
+ // value={parseConfig?.tooltip?.borderWidth || 0}
1645
+ // min={0}
1646
+ // suffix="px"
1647
+ // onChange={(v) =>
1648
+ // transformReturn({ tooltip: { borderWidth: v } })
1649
+ // }
1650
+ // />
1651
+ // </Form.Item>
1652
+ // <Form.Item
1653
+ // label="浮层内边距"
1654
+ // labelCol={{ span: 12 }}
1655
+ // wrapperCol={{ span: 12 }}
1656
+ // >
1657
+ // <InputNumber
1658
+ // size="small"
1659
+ // value={parseConfig?.tooltip?.padding || 5}
1660
+ // min={0}
1661
+ // suffix="px"
1662
+ // onChange={(v) =>
1663
+ // transformReturn({ tooltip: { padding: v } })
1664
+ // }
1665
+ // />
1666
+ // </Form.Item>
1667
+ // <Form.Item
1668
+ // label="内容格式器"
1669
+ // layout="vertical"
1670
+ // tooltip={
1671
+ // <div>
1672
+ // {`模板变量有 {a}, {b},{c},{d},{e},分别表示系列名,数据名,数据值等。 在 trigger 为 'axis' 的时候,会有多个系列的数据,此时可以通过 {a0}, {a1}, {a2} 这种后面加索引的方式表示系列的索引。 不同图表类型下的 {a},{b},{c},{d} 含义不一样。 其中变量{a}, {b}, {c}, {d}在不同图表类型下代表数据含义为:`}
1673
+ // <ul>
1674
+ // <li>{`折线(区域)图、柱状(条形)图、K线图 : {a}(系列名称),{b}(类目值),{c}(数值), {d}(无)`}</li>
1675
+ // <li>{`散点图(气泡)图 : {a}(系列名称),{b}(数据名称),{c}(数值数组), {d}(无)`}</li>
1676
+ // <li>{`地图 : {a}(系列名称),{b}(区域名称),{c}(合并数值), {d}(无)`}</li>
1677
+ // <li>{`饼图、仪表盘、漏斗图: {a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比)`}</li>
1678
+ // </ul>
1679
+ // </div>
1680
+ // }
1681
+ // >
1682
+ // <Input.TextArea
1683
+ // value={parseConfig?.tooltip?.formatter}
1684
+ // onChange={(e) =>
1685
+ // transformReturn({
1686
+ // tooltip: { formatter: e.target.value },
1687
+ // })
1688
+ // }
1689
+ // />
1690
+ // </Form.Item>
1691
+ // </Space>
1692
+ // ),
1693
+ // },
1694
+ {
1695
+ key: "custom",
1696
+ label: "自定义配置",
1697
+ collapsible: "icon",
1698
+ children: (
1699
+ <Form.Item
1700
+ layout="vertical"
1701
+ label={
1702
+ <>
1703
+ <Space>
1704
+ Echarts 渲染脚本{" "}
1705
+ <Tooltip
1706
+ title={
1707
+ <>
1708
+ 模板语法参考
1709
+ <ul style={{ padding: 0, listStyle: "none" }}>
1710
+ <li>变量调用:{`{{变量名}}`}</li>
1711
+ <li>环境变量:{`{{$e.变量名}}`}</li>
1712
+ <li>全局变量:{`{{$g.变量名}}`}</li>
1713
+ <li>函数参数:{`{{data}}`}</li>
1714
+ </ul>
1715
+ </>
1716
+ }
1717
+ >
1718
+ <InfoCircleOutlined />
1719
+ </Tooltip>
1720
+ </Space>
1721
+ <Space>
1722
+ <a onClick={() => setFullScreen(true)}>
1723
+ <FullscreenOutlined /> 全屏
1724
+ </a>
1725
+ <a onClick={format}>
1726
+ <IconFont type="icon-formate" /> 格式化
1727
+ </a>
1728
+ </Space>
1729
+ </>
1730
+ }
1731
+ className="ant-form-item-label-space"
1732
+ >
1733
+ <div
1734
+ style={{
1735
+ border: "1px solid #d9d9d9",
1736
+ borderRadius: 4,
1737
+ textAlign: "left",
1738
+ }}
1739
+ >
1740
+ <div
1741
+ style={{
1742
+ color: "#0000ff",
1743
+ fontSize: 14,
1744
+ padding: "0 50px",
1745
+ }}
1746
+ >
1747
+ function getOptions(data) {"{"}{" "}
1748
+ </div>
1749
+ <Editor
1750
+ onMount={(editor) => {
1751
+ (ref.current as any) = { editor };
1752
+ }}
1753
+ height="400px"
1754
+ defaultLanguage="javascript"
1755
+ value={model.script}
1756
+ onChange={(v) => setScript(v || "")}
1757
+ options={{
1758
+ minimap: { enabled: false },
1759
+ scrollBeyondLastLine: false,
1760
+ tabSize: 2,
1761
+ }}
1762
+ />
1763
+ <div
1764
+ style={{
1765
+ color: "#0000ff",
1766
+ fontSize: 14,
1767
+ padding: "0 50px",
1768
+ }}
1769
+ >
1770
+ {"}"}
1771
+ </div>
1772
+ </div>
1773
+ </Form.Item>
1774
+ ),
1775
+ },
1776
+ ]}
1777
+ style={{ marginBottom: 14 }}
1778
+ />
1779
+
1780
+ <Drawer
1781
+ title={
1782
+ <Flex justify="space-between" align="center">
1783
+ Echarts 渲染脚本
1784
+ <Space>
1785
+ <a key="format" onClick={fullFormat}>
1786
+ <IconFont type="icon-formate" /> 格式化
1787
+ </a>
1788
+ </Space>
1789
+ </Flex>
66
1790
  }
67
- className="ant-form-item-label-space"
1791
+ open={fullScreen}
1792
+ width="100%"
1793
+ onClose={() => setFullScreen(false)}
1794
+ styles={{
1795
+ body: {
1796
+ padding: 0,
1797
+ },
1798
+ }}
68
1799
  >
69
1800
  <div
70
1801
  style={{
71
- border: "1px solid #d9d9d9",
72
- borderRadius: 4,
73
1802
  textAlign: "left",
74
1803
  }}
75
1804
  >
@@ -78,9 +1807,9 @@ export const EchartsProps: React.FC<PropEditorProps<EchartsModel>> = ({
78
1807
  </div>
79
1808
  <Editor
80
1809
  onMount={(editor) => {
81
- (ref.current as any) = { editor };
1810
+ (fullRef.current as any) = { editor };
82
1811
  }}
83
- height="400px"
1812
+ height="calc(100vh - 100px)"
84
1813
  defaultLanguage="javascript"
85
1814
  value={model.script}
86
1815
  onChange={(v) => setScript(v || "")}
@@ -94,7 +1823,7 @@ export const EchartsProps: React.FC<PropEditorProps<EchartsModel>> = ({
94
1823
  {"}"}
95
1824
  </div>
96
1825
  </div>
97
- </Form.Item>
1826
+ </Drawer>
98
1827
  </Form>
99
1828
  );
100
1829
  };