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,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useRef, useState } from "react";
2
2
  import {
3
3
  Form,
4
4
  Input,
@@ -8,9 +8,18 @@ import {
8
8
  Button,
9
9
  Space,
10
10
  type SelectProps as AntSelectProps,
11
+ Tooltip,
12
+ Drawer,
13
+ Flex,
11
14
  } from "antd";
12
15
  import type { PropEditorProps } from "./types";
13
- import { DeleteOutlined } from "@ant-design/icons";
16
+ import {
17
+ DeleteOutlined,
18
+ FullscreenOutlined,
19
+ InfoCircleOutlined,
20
+ } from "@ant-design/icons";
21
+ import { IconFont } from "../../../icon/IconFont";
22
+ import { Editor } from "@monaco-editor/react";
14
23
 
15
24
  export type SelectOption = { label: string; value: any };
16
25
  export type SelectModel = {
@@ -19,6 +28,8 @@ export type SelectModel = {
19
28
  size?: AntSelectProps["size"];
20
29
  allowClear?: boolean;
21
30
  multiple?: boolean;
31
+ source?: "local" | "script";
32
+ globalDatasetScript?: string;
22
33
  options: SelectOption[];
23
34
  };
24
35
 
@@ -26,6 +37,9 @@ export const SelectProps: React.FC<PropEditorProps<SelectModel>> = ({
26
37
  model,
27
38
  onChange,
28
39
  }) => {
40
+ const ref = useRef<any>(null);
41
+ const fullRef = useRef<any>(null);
42
+ const [fullScreen, setFullScreen] = useState(false);
29
43
  const trigger = (key: keyof SelectModel, value: any) =>
30
44
  onChange && onChange({ ...model, [key]: value });
31
45
  const setOption = (index: number, key: keyof SelectOption, value: any) => {
@@ -41,6 +55,16 @@ export const SelectProps: React.FC<PropEditorProps<SelectModel>> = ({
41
55
  const addOption = () => {
42
56
  trigger("options", [...(model.options || []), { label: "", value: "" }]);
43
57
  };
58
+ const format = () => {
59
+ // Monaco-react does not expose format directly; relying on editor action
60
+ const editor = (ref.current as any)?.editor;
61
+ editor?.getAction("editor.action.formatDocument")?.run();
62
+ };
63
+
64
+ const fullFormat = () => {
65
+ const editor = (fullRef.current as any)?.editor;
66
+ editor?.getAction("editor.action.formatDocument")?.run();
67
+ };
44
68
  const columns = [
45
69
  {
46
70
  title: "选项标签",
@@ -118,19 +142,156 @@ export const SelectProps: React.FC<PropEditorProps<SelectModel>> = ({
118
142
  onChange={(v) => trigger("multiple", v)}
119
143
  />
120
144
  </Form.Item>
121
- <Form.Item labelCol={{ span: 24 }} wrapperCol={{ span: 24 }}>
122
- <Table
145
+ <Form.Item label="数据来源">
146
+ <Radio.Group
123
147
  size="small"
124
- dataSource={model.options}
125
- columns={columns as any}
126
- pagination={false}
127
- rowKey={(_, i) => String(i)}
128
- bordered
129
- />
130
- <Button size="small" block onClick={addOption}>
131
- 添加选项
132
- </Button>
148
+ value={model.source || "local"}
149
+ onChange={(e) => trigger("source", e.target.value)}
150
+ >
151
+ <Radio.Button value="local">本地选项</Radio.Button>
152
+ <Radio.Button value="script">JS 脚本</Radio.Button>
153
+ </Radio.Group>
133
154
  </Form.Item>
155
+ {model.source === "script" && (
156
+ <Form.Item
157
+ layout="vertical"
158
+ labelCol={{ span: 24 }}
159
+ wrapperCol={{ span: 24 }}
160
+ label={
161
+ <>
162
+ <Space>
163
+ JS 脚本{" "}
164
+ <Tooltip
165
+ title={
166
+ <>
167
+ 模板语法参考
168
+ <ul style={{ padding: 0, listStyle: "none" }}>
169
+ <li>变量调用:{`{{变量名}}`}</li>
170
+ <li>全局变量:{`{{$g.变量名}}`}</li>
171
+ <li>返回值:{`return {label: string, value: string|number}[]`}</li>
172
+ </ul>
173
+ </>
174
+ }
175
+ >
176
+ <InfoCircleOutlined />
177
+ </Tooltip>
178
+ </Space>
179
+ <Space>
180
+ <a onClick={() => setFullScreen(true)}>
181
+ <FullscreenOutlined /> 全屏
182
+ </a>
183
+ <a onClick={format}>
184
+ <IconFont type="icon-formate" /> 格式化
185
+ </a>
186
+ </Space>
187
+ </>
188
+ }
189
+ className="ant-form-item-label-space"
190
+ >
191
+ <div
192
+ style={{
193
+ border: "1px solid #d9d9d9",
194
+ borderRadius: 4,
195
+ textAlign: "left",
196
+ }}
197
+ >
198
+ <div
199
+ style={{
200
+ color: "#0000ff",
201
+ fontSize: 14,
202
+ padding: "0 50px",
203
+ }}
204
+ >
205
+ function getOptions(data) {"{"}{" "}
206
+ </div>
207
+ <Editor
208
+ onMount={(editor) => {
209
+ (ref.current as any) = { editor };
210
+ }}
211
+ height="400px"
212
+ defaultLanguage="javascript"
213
+ value={model.globalDatasetScript}
214
+ onChange={(v) => trigger("globalDatasetScript", v || "")}
215
+ options={{
216
+ minimap: { enabled: false },
217
+ scrollBeyondLastLine: false,
218
+ tabSize: 2,
219
+ }}
220
+ />
221
+ <div
222
+ style={{
223
+ color: "#0000ff",
224
+ fontSize: 14,
225
+ padding: "0 50px",
226
+ }}
227
+ >
228
+ {"}"}
229
+ </div>
230
+ </div>
231
+ </Form.Item>
232
+ )}
233
+ {model.source !== "script" && (
234
+ <Form.Item labelCol={{ span: 24 }} wrapperCol={{ span: 24 }}>
235
+ <Table
236
+ size="small"
237
+ dataSource={model.options}
238
+ columns={columns as any}
239
+ pagination={false}
240
+ rowKey={(_, i) => String(i)}
241
+ bordered
242
+ />
243
+ <Button size="small" block onClick={addOption}>
244
+ 添加选项
245
+ </Button>
246
+ </Form.Item>
247
+ )}
248
+ <Drawer
249
+ title={
250
+ <Flex justify="space-between" align="center">
251
+ JS 脚本
252
+ <Space>
253
+ <a key="format" onClick={fullFormat}>
254
+ <IconFont type="icon-formate" /> 格式化
255
+ </a>
256
+ </Space>
257
+ </Flex>
258
+ }
259
+ open={fullScreen}
260
+ width="100%"
261
+ onClose={() => setFullScreen(false)}
262
+ styles={{
263
+ body: {
264
+ padding: 0,
265
+ },
266
+ }}
267
+ >
268
+ <div
269
+ style={{
270
+ textAlign: "left",
271
+ }}
272
+ >
273
+ <div style={{ color: "#0000ff", fontSize: 14, padding: "0 50px" }}>
274
+ function getOptions(data) {"{"}{" "}
275
+ </div>
276
+ <Editor
277
+ onMount={(editor) => {
278
+ (fullRef.current as any) = { editor };
279
+ }}
280
+ height="calc(100vh - 100px)"
281
+ defaultLanguage="javascript"
282
+ value={model.globalDatasetScript}
283
+ onChange={(v) => trigger("globalDatasetScript", v || "")}
284
+ options={{
285
+ minimap: { enabled: false },
286
+ scrollBeyondLastLine: false,
287
+ tabSize: 2,
288
+ }}
289
+ />
290
+ <div style={{ color: "#0000ff", fontSize: 14, padding: "0 50px" }}>
291
+ {"}"}
292
+ </div>
293
+ </div>
294
+ </Drawer>
134
295
  </Form>
135
296
  );
136
297
  };
@@ -1,5 +1,5 @@
1
1
  import { Select, type SelectProps } from "antd";
2
- import React, { useContext } from "react";
2
+ import React, { useContext, useMemo } from "react";
3
3
  import { PageContext } from "../../../context/PageContext";
4
4
  import { HtmlBaseProps, SchemaItemType } from "../../../typing";
5
5
  import { useEvent } from "../../../hooks/event";
@@ -10,6 +10,8 @@ export type SelectRenderProps = {
10
10
  placeholder?: string;
11
11
  allowClear?: boolean;
12
12
  multiple?: boolean;
13
+ source?: "local" | "script";
14
+ globalDatasetScript?: string;
13
15
  options?: { label: string; value: any }[];
14
16
  item: SchemaItemType;
15
17
  value?: any;
@@ -23,6 +25,8 @@ export const SelectRender: React.FC<SelectRenderProps> = ({
23
25
  placeholder = "请选择",
24
26
  allowClear = false,
25
27
  multiple = false,
28
+ source = "local",
29
+ globalDatasetScript = "",
26
30
  options = [],
27
31
  item,
28
32
  value,
@@ -30,7 +34,7 @@ export const SelectRender: React.FC<SelectRenderProps> = ({
30
34
  style = {},
31
35
  className,
32
36
  }) => {
33
- const { handleCallback, setVar } = useContext(PageContext);
37
+ const { handleCallback, setVar, globalVars } = useContext(PageContext);
34
38
  const { handleEvent } = useEvent(item);
35
39
  const change = (v: any) => {
36
40
  if (onChange) onChange(v);
@@ -38,6 +42,51 @@ export const SelectRender: React.FC<SelectRenderProps> = ({
38
42
  handleCallback(item);
39
43
  handleEvent("change", v);
40
44
  };
45
+
46
+ const computedOptions = useMemo(() => {
47
+ if (source === "script") {
48
+ try {
49
+ const safeEval = new Function(
50
+ "console",
51
+ "Math",
52
+ "Date",
53
+ "Array",
54
+ "Object",
55
+ "String",
56
+ "Number",
57
+ "Boolean",
58
+ "$g",
59
+ `
60
+ "use strict";
61
+ const window = undefined;
62
+ const document = undefined;
63
+ const global = undefined;
64
+ const process = undefined;
65
+ const require = undefined;
66
+ const module = undefined;
67
+ const exports = undefined;
68
+ ${globalDatasetScript}
69
+ `,
70
+ );
71
+ return safeEval(
72
+ console,
73
+ Math,
74
+ Date,
75
+ Array,
76
+ Object,
77
+ String,
78
+ Number,
79
+ Boolean,
80
+ globalVars,
81
+ );
82
+ } catch (error) {
83
+ console.error("Options 脚本执行错误", error);
84
+ return [];
85
+ }
86
+ }
87
+ return options;
88
+ }, [source, options, globalVars]);
89
+
41
90
  return (
42
91
  <Select
43
92
  id={id}
@@ -46,7 +95,7 @@ export const SelectRender: React.FC<SelectRenderProps> = ({
46
95
  placeholder={placeholder}
47
96
  allowClear={allowClear}
48
97
  mode={multiple ? "multiple" : undefined}
49
- options={options}
98
+ options={computedOptions}
50
99
  onChange={change}
51
100
  style={{ width: "100%", ...style }}
52
101
  className={className}