bi-sdk-react 0.0.75 → 0.0.77

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