bi-sdk-react 0.0.63 → 0.0.65

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.65",
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[];
@@ -188,7 +201,12 @@ const Toolbar: React.FC<{
188
201
  </Tooltip>
189
202
  )}
190
203
  <Tooltip title="源码编辑">
191
- <a onClick={() => setVisible(true)}>
204
+ <a
205
+ onClick={(e) => {
206
+ e.stopPropagation();
207
+ setVisible(true);
208
+ }}
209
+ >
192
210
  <IconFont type="icon-json" />
193
211
  </a>
194
212
  </Tooltip>
@@ -241,6 +259,8 @@ export const PageItem: React.FC<PageItemProps> = ({
241
259
  onRemoveAtItem,
242
260
  forceUpdate,
243
261
  fetch,
262
+ globalVars,
263
+ env,
244
264
  } = useContext(PageContext);
245
265
  const [isDragging, setIsDragging] = useState(false);
246
266
  const [toolbarContainer, setToolbarContainer] =
@@ -274,6 +294,33 @@ export const PageItem: React.FC<PageItemProps> = ({
274
294
  | React.ComponentType<any>
275
295
  | undefined;
276
296
 
297
+ const hidden = useMemo(() => {
298
+ const expression = item.hideExpression;
299
+ if (!expression?.length) {
300
+ return false;
301
+ }
302
+ try {
303
+ const func = new Function(
304
+ "$g",
305
+ "$e",
306
+ `
307
+ "use strict";
308
+ const window = undefined;
309
+ const document = undefined;
310
+ const global = undefined;
311
+ const process = undefined;
312
+ const require = undefined;
313
+ const module = undefined;
314
+ const exports = undefined;
315
+ return ${expression} === true;
316
+ `,
317
+ );
318
+ return func(globalVars, env.global);
319
+ } catch (e) {
320
+ return false;
321
+ }
322
+ }, [item.hideExpression, globalVars, env]);
323
+
277
324
  useEffect(() => {
278
325
  const updateToolbarPosition = () => {
279
326
  if (!toolbarEl.current) return;
@@ -316,7 +363,7 @@ export const PageItem: React.FC<PageItemProps> = ({
316
363
 
317
364
  if (selectedItem && selectedItem === item && designable) {
318
365
  const div = document.createElement("div");
319
- div.setAttribute("id", "page-item-toolbar")
366
+ div.setAttribute("id", "page-item-toolbar");
320
367
  div.style.position = "absolute";
321
368
  div.style.zIndex = "1000";
322
369
  div.style.display = "flex";
@@ -370,6 +417,33 @@ export const PageItem: React.FC<PageItemProps> = ({
370
417
 
371
418
  if (!Comp) return null;
372
419
 
420
+ if (hidden) {
421
+ return designable ? (
422
+ <>
423
+ <Hidden
424
+ id={`page-item-${item.id}`}
425
+ data-item-id={item.id}
426
+ className={`page-item-component ${designable ? "designable" : ""} ${
427
+ selectedItem === item ? "selected" : ""
428
+ }`}
429
+ ></Hidden>
430
+ {toolbarContainer &&
431
+ ReactDOM.createPortal(
432
+ <Toolbar
433
+ item={item}
434
+ ancestors={ancestors}
435
+ onAddAt={fetch?.ai?.chat ? handleAddAtItem : undefined}
436
+ onCopy={handleCopy}
437
+ onDelete={handleDelete}
438
+ onUpdate={forceUpdate}
439
+ onSelect={onSelect}
440
+ />,
441
+ toolbarContainer,
442
+ )}
443
+ </>
444
+ ) : null;
445
+ }
446
+
373
447
  return (
374
448
  <>
375
449
  <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[];