bi-sdk-react 0.0.63 → 0.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bi-sdk-react",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/umd/js/bi-sdk.umd.min.js",
@@ -7,12 +7,27 @@ import {
7
7
  MobileOutlined,
8
8
  RedoOutlined,
9
9
  SaveOutlined,
10
+ SearchOutlined,
10
11
  TabletOutlined,
11
12
  UndoOutlined,
12
13
  ZoomInOutlined,
13
14
  ZoomOutOutlined,
14
15
  } from "@ant-design/icons";
15
- import { Button, Divider, Drawer, Modal, Radio, Space, Tooltip } from "antd";
16
+ import {
17
+ AutoComplete,
18
+ Button,
19
+ Divider,
20
+ Drawer,
21
+ Empty,
22
+ Flex,
23
+ Input,
24
+ Modal,
25
+ Radio,
26
+ Space,
27
+ Tag,
28
+ Tooltip,
29
+ Typography,
30
+ } from "antd";
16
31
  import React, { useImperativeHandle, useMemo, useRef, useState } from "react";
17
32
  import styled from "styled-components";
18
33
  import { IconFont } from "./icon/IconFont";
@@ -189,6 +204,63 @@ const Container = styled.div`
189
204
  }
190
205
  `;
191
206
 
207
+ const Search = styled(Flex)`
208
+ color: var(--ant-color-text-label);
209
+ background: var(--ant-color-bg-text-hover);
210
+ border: var(--ant-color-border) solid 1px;
211
+ padding: 0 20px 0 8px;
212
+ border-radius: var(--ant-border-radius);
213
+ height: 24px;
214
+ font-size: 12px;
215
+ cursor: pointer;
216
+ user-select: none;
217
+
218
+ &:hover {
219
+ background: var(--ant-color-bg-text-active);
220
+ }
221
+ `;
222
+
223
+ const SearchList = styled.ul`
224
+ padding: 0;
225
+ margin: 0;
226
+ list-style: none;
227
+ li {
228
+ padding: 10px;
229
+ border-bottom: dotted 1px var(--ant-color-border);
230
+ cursor: pointer;
231
+
232
+ &:last-child {
233
+ border-bottom: none;
234
+ }
235
+
236
+ &:hover {
237
+ background: var(--ant-color-primary-bg);
238
+ }
239
+
240
+ h5 {
241
+ margin: 0;
242
+ color: var(--ant-color-text-label);
243
+ font-size: 14px;
244
+
245
+ small {
246
+ color: var(--ant-color-text-description);
247
+
248
+ &:before {
249
+ content: "(ID: ";
250
+ }
251
+ &:after {
252
+ content: ")";
253
+ }
254
+ }
255
+ }
256
+
257
+ div {
258
+ margin: 8px 0 0 0;
259
+ color: var(--ant-color-text-description);
260
+ }
261
+ }
262
+ `;
263
+
192
264
  const ImportModal: React.FC<{
193
265
  open?: boolean;
194
266
  onCancel?: () => void;
@@ -287,6 +359,8 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
287
359
  const [designable, setDesignable] = useState(true);
288
360
  const [importModalOpen, setImportModalOpen] = useState(false);
289
361
  const [aiPaneOpen, setAiPaneOpen] = useState(false);
362
+ const [searchKey, setSearchKey] = useState("");
363
+ const [searchOpen, setSearchOpen] = useState(false);
290
364
  const containerStyle = useMemo(() => {
291
365
  const left = showLeft ? "250px" : "";
292
366
  const right = showRight ? "400px" : "";
@@ -470,11 +544,34 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
470
544
  return rightPanelActiveKey;
471
545
  }, [selectedItem, rightPanelActiveKey]);
472
546
 
473
-
474
547
  const selectedPlugin: PluginType | undefined = useMemo(() => {
475
548
  return (plugins || []).find((p) => p.key === selectedItem?.type);
476
549
  }, [plugins, selectedItem?.type]);
477
550
 
551
+ const searchedItems: SchemaItemType[] = useMemo(() => {
552
+ if (!searchKey?.trim()?.length) {
553
+ return [];
554
+ }
555
+ const list: SchemaItemType[] = [];
556
+ const compute = (arr?: any[]) => {
557
+ if (!Array.isArray(arr)) return;
558
+ arr.forEach((item) => {
559
+ if (JSON.stringify(item).includes(searchKey)) {
560
+ list.push(item);
561
+ }
562
+ if (Array.isArray(item.children)) {
563
+ compute(item.children);
564
+ } else if (typeof item.children === "object" && item.children) {
565
+ Object.keys(item.children).forEach((k) =>
566
+ compute(item.children[k]),
567
+ );
568
+ }
569
+ });
570
+ };
571
+ compute(schema?.items || []);
572
+ return list;
573
+ }, [schema?.items, searchKey]);
574
+
478
575
  return (
479
576
  <PageProvider
480
577
  pageId={pageId}
@@ -673,7 +770,9 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
673
770
  renderNode={datasetPanel}
674
771
  />
675
772
  )}
676
- {leftPanelActiveKey === "global-dataset" && <GlobalDatasetPanel />}
773
+ {leftPanelActiveKey === "global-dataset" && (
774
+ <GlobalDatasetPanel />
775
+ )}
677
776
  </div>
678
777
  </div>
679
778
  <div className="center" style={{ flex: "1 1 auto" }}>
@@ -681,6 +780,21 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
681
780
  title="页面"
682
781
  extra={
683
782
  <Space>
783
+ <Search
784
+ align="center"
785
+ gap={8}
786
+ onClick={() => setSearchOpen(true)}
787
+ >
788
+ <SearchOutlined />
789
+ <Typography.Text
790
+ style={{
791
+ fontSize: 12,
792
+ color: "var(--ant-color-text-label)",
793
+ }}
794
+ >
795
+ Quick search...
796
+ </Typography.Text>
797
+ </Search>
684
798
  <Button
685
799
  size="small"
686
800
  type={!designable ? "primary" : "default"}
@@ -709,6 +823,7 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
709
823
  }
710
824
  />
711
825
  <div
826
+ id="page-canvas-wrapper"
712
827
  className={[
713
828
  "body beautified_scrollbar always",
714
829
  deviceType,
@@ -864,6 +979,85 @@ export const PageDesigner = React.forwardRef<any, PageDesignerProps>(
864
979
  onEffect={handleAiEffect}
865
980
  />
866
981
  </Drawer>
982
+ <Modal
983
+ open={searchOpen}
984
+ title={
985
+ <Input
986
+ placeholder="搜索页面元素 ..."
987
+ variant="borderless"
988
+ prefix={
989
+ <SearchOutlined
990
+ style={{ color: "var(--ant-color-text-label)" }}
991
+ />
992
+ }
993
+ value={searchKey}
994
+ onChange={(e) => setSearchKey(e.target.value)}
995
+ />
996
+ }
997
+ closeIcon={
998
+ <Typography.Link
999
+ unselectable="on"
1000
+ style={{ color: "var(--ant-color-text-quaternary)" }}
1001
+ >
1002
+ ESC
1003
+ </Typography.Link>
1004
+ }
1005
+ footer={null}
1006
+ onCancel={() => setSearchOpen(false)}
1007
+ styles={{
1008
+ container: { padding: 0 },
1009
+ header: {
1010
+ padding: "16px 12px",
1011
+ borderBottom: "solid 1px var(--ant-color-border)",
1012
+ },
1013
+ body: {
1014
+ padding: "0 12px 12px 12px",
1015
+ overflow: "auto",
1016
+ maxHeight: 500,
1017
+ },
1018
+ }}
1019
+ >
1020
+ <SearchList>
1021
+ {searchedItems.length === 0 ? (
1022
+ <Empty
1023
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
1024
+ description={
1025
+ searchKey?.trim()?.length
1026
+ ? "未找到页面元素"
1027
+ : "请输入关键字进行搜索..."
1028
+ }
1029
+ />
1030
+ ) : (
1031
+ searchedItems.map((item) => (
1032
+ <li
1033
+ key={item.id}
1034
+ onClick={() => {
1035
+ setSelectedItem(item);
1036
+ document
1037
+ .getElementById(`page-item-${item.id}`)
1038
+ ?.scrollIntoView({ behavior: "smooth" });
1039
+ setSearchOpen(false);
1040
+ }}
1041
+ >
1042
+ <Typography.Title level={5} style={{ margin: 0 }}>
1043
+ <Tag
1044
+ variant="filled"
1045
+ color="#108ee9"
1046
+ style={{ marginRight: 8 }}
1047
+ >
1048
+ 页面元素
1049
+ </Tag>
1050
+ {item.name}
1051
+ <small>{item.id}</small>
1052
+ </Typography.Title>
1053
+ <Typography.Paragraph>
1054
+ {item.description || "无描述内容"}
1055
+ </Typography.Paragraph>
1056
+ </li>
1057
+ ))
1058
+ )}
1059
+ </SearchList>
1060
+ </Modal>
867
1061
  </PageProvider>
868
1062
  );
869
1063
  },
@@ -1,5 +1,5 @@
1
1
  import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
2
- import React, { useContext, useEffect, useRef, useState } from "react";
2
+ import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { PageContext } from "../context/PageContext";
4
4
  import { SchemaItemType } from "../typing";
5
5
  import styled from "styled-components";
@@ -63,6 +63,19 @@ const ToolbarWrap = styled.div`
63
63
  }
64
64
  `;
65
65
 
66
+ const Hidden = styled.div`
67
+ user-select: none;
68
+ font-size: 12px;
69
+ text-align: center;
70
+ color: var(--ant-color-text-placeholder);
71
+ background: var(--ant-color-bg-layout);
72
+ border: dotted 1px #ccc;
73
+
74
+ &:before {
75
+ content: "组件已隐藏";
76
+ }
77
+ `;
78
+
66
79
  const Toolbar: React.FC<{
67
80
  item: SchemaItemType;
68
81
  ancestors: SchemaItemType[];
@@ -241,6 +254,8 @@ export const PageItem: React.FC<PageItemProps> = ({
241
254
  onRemoveAtItem,
242
255
  forceUpdate,
243
256
  fetch,
257
+ globalVars,
258
+ env,
244
259
  } = useContext(PageContext);
245
260
  const [isDragging, setIsDragging] = useState(false);
246
261
  const [toolbarContainer, setToolbarContainer] =
@@ -274,6 +289,33 @@ export const PageItem: React.FC<PageItemProps> = ({
274
289
  | React.ComponentType<any>
275
290
  | undefined;
276
291
 
292
+ const hidden = useMemo(() => {
293
+ const expression = item.hideExpression;
294
+ if (!expression?.length) {
295
+ return false;
296
+ }
297
+ try {
298
+ const func = new Function(
299
+ "$g",
300
+ "$e",
301
+ `
302
+ "use strict";
303
+ const window = undefined;
304
+ const document = undefined;
305
+ const global = undefined;
306
+ const process = undefined;
307
+ const require = undefined;
308
+ const module = undefined;
309
+ const exports = undefined;
310
+ return ${expression} === true;
311
+ `,
312
+ );
313
+ return func(globalVars, env.global);
314
+ } catch (e) {
315
+ return false;
316
+ }
317
+ }, [item.hideExpression, globalVars, env]);
318
+
277
319
  useEffect(() => {
278
320
  const updateToolbarPosition = () => {
279
321
  if (!toolbarEl.current) return;
@@ -316,7 +358,7 @@ export const PageItem: React.FC<PageItemProps> = ({
316
358
 
317
359
  if (selectedItem && selectedItem === item && designable) {
318
360
  const div = document.createElement("div");
319
- div.setAttribute("id", "page-item-toolbar")
361
+ div.setAttribute("id", "page-item-toolbar");
320
362
  div.style.position = "absolute";
321
363
  div.style.zIndex = "1000";
322
364
  div.style.display = "flex";
@@ -370,6 +412,33 @@ export const PageItem: React.FC<PageItemProps> = ({
370
412
 
371
413
  if (!Comp) return null;
372
414
 
415
+ if (hidden) {
416
+ return designable ? (
417
+ <>
418
+ <Hidden
419
+ id={`page-item-${item.id}`}
420
+ data-item-id={item.id}
421
+ className={`page-item-component ${designable ? "designable" : ""} ${
422
+ selectedItem === item ? "selected" : ""
423
+ }`}
424
+ ></Hidden>
425
+ {toolbarContainer &&
426
+ ReactDOM.createPortal(
427
+ <Toolbar
428
+ item={item}
429
+ ancestors={ancestors}
430
+ onAddAt={fetch?.ai?.chat ? handleAddAtItem : undefined}
431
+ onCopy={handleCopy}
432
+ onDelete={handleDelete}
433
+ onUpdate={forceUpdate}
434
+ onSelect={onSelect}
435
+ />,
436
+ toolbarContainer,
437
+ )}
438
+ </>
439
+ ) : null;
440
+ }
441
+
373
442
  return (
374
443
  <>
375
444
  <Comp
@@ -229,6 +229,9 @@ export const LayerPanel: React.FC = () => {
229
229
  onSelect={(_, info: any) => {
230
230
  if (info.node.dataRef.type === "slot") return;
231
231
  onSelect && onSelect(info.node.dataRef.item);
232
+ document
233
+ .getElementById(`page-item-${info.node.dataRef.item.id}`)
234
+ ?.scrollIntoView({ behavior: "smooth" });
232
235
  }}
233
236
  onDrop={onDrop}
234
237
  onExpand={(keys) => setExpandedKeys(keys as string[])}
@@ -271,6 +271,26 @@ export const PropertiesPanel: React.FC<{
271
271
  }
272
272
  />
273
273
  </Form.Item>
274
+ <Form.Item label="隐藏组件" tooltip={
275
+ <>
276
+ <div>当下方表达式(JS)不为空且为true时组件隐藏。</div>
277
+ <div><small>1.环境变量:$e.变量名。</small></div>
278
+ <div><small>2.全局变量:$g.变量名。</small></div>
279
+ <div><small>示例:$e.a === '1'。</small></div>
280
+ </>
281
+ }>
282
+ <Input.TextArea
283
+ size="small"
284
+ value={selectedItem?.hideExpression}
285
+ onChange={(e) =>
286
+ onUpdateSelectedItem &&
287
+ onUpdateSelectedItem({
288
+ ...selectedItem,
289
+ hideExpression: e.target.value,
290
+ })
291
+ }
292
+ />
293
+ </Form.Item>
274
294
  </Form>
275
295
  {selectedItem?.datasource && <Divider>数据源</Divider>}
276
296
  {selectedItem?.datasource && (
@@ -36,7 +36,7 @@ export const HtmlRender: React.FC<HtmlRenderProps> = ({
36
36
  const [signal, setSignal] = useState<number>(0);
37
37
  const datasource = useDatasource({ item, signal });
38
38
 
39
- const keys = useMemo(() => Object.keys(datasource || {}), [datasource]);
39
+ const keys = useMemo(() => Array.isArray(datasource) ? [] : Object.keys(datasource || {}), [datasource]);
40
40
  const func = (path: string) =>
41
41
  new Function(
42
42
  "$g",
@@ -94,6 +94,7 @@ export type SchemaItemType = {
94
94
  type: string;
95
95
  name?: string;
96
96
  description?: string;
97
+ hideExpression?: string;
97
98
  style?: React.CSSProperties;
98
99
  props?: Record<string, any>;
99
100
  cascadeIds?: string[];