Commit 04129d565215a68f370e114fd2b5491455fccd46

Authored by 陈洋
2 parents 32b245be 2122a03e

Merge remote-tracking branch 'origin/feature/dataflow' into feature/dataflow

1 1 {
2 2 "name": "@qx/common",
3   - "version": "3.0.0-alpha.19",
  3 + "version": "3.0.0-alpha.21",
4 4 "description": "A react library developed with dumi",
5 5 "license": "MIT",
6 6 "module": "dist/index.js",
... ...
... ... @@ -15,5 +15,7 @@ export * from './qx-btn';
15 15 export * from './qx-progress';
16 16 export * from './qx-search-input';
17 17 export * from './qx-dynamic-component';
  18 +export * from './qx-widget-icon';
  19 +export * from './qx-flow-node-selector';
18 20
19 21
... ...
... ... @@ -53,16 +53,10 @@
53 53 color: @N7;
54 54 }
55 55
56   - &-value-render {
57   - position: absolute;
58   - background-color: #fff;
59   - width: calc(100% - 33px);
60   - height: 30px;
61   - display: flex;
62   - align-items: center;
63   - top: 1px;
64   - left: 8px;
65   - z-index: 1;
  56 + .qx-field-setter {
  57 + .ant-tag {
  58 + display: flex;
  59 + }
66 60 }
67 61 }
68 62
... ...
1 1 import { ControlOutlined } from '@ant-design/icons';
2   -import { Select, Tooltip, Input } from 'antd';
  2 +import { Select, Tooltip } from 'antd';
3 3 import { size } from 'lodash-es';
4 4 import React, { useState } from 'react';
5 5 import type { QxBaseConditionField } from '../qx-base-condition';
6 6 import { QxBaseIcon } from '../qx-base-icon';
7 7 import { QxFieldSetter } from '../qx-field-setter';
  8 +import type { INode } from '../qx-flow-node-selector';
  9 +import {
  10 + QxFlowNodeFieldSelector,
  11 + useNodeFieldDisplay,
  12 +} from '../qx-flow-node-selector';
  13 +import { QxWidgetIcon } from '../qx-widget-icon';
8 14
9 15 import './index.less';
10 16
... ... @@ -666,147 +672,22 @@ const optValTypeCheck = {
666 672 },
667 673 };
668 674
669   -export const WidgetsIcon = ({ widgetName }: { widgetName: string }) => {
670   - let iconType: string = '';
671   - switch (widgetName) {
672   - case 'qxInput':
673   - iconType = 'icon-field-text';
674   - break;
675   - case 'qxNumber':
676   - iconType = 'icon-field-num';
677   - break;
678   - case 'dateTime':
679   - case 'qxDatetime':
680   - iconType = 'icon-field-datetime';
681   - break;
682   - case 'qxTime':
683   - iconType = 'icon-field-time';
684   - break;
685   - case 'qxSwitch':
686   - iconType = 'icon-field-boolean';
687   - break;
688   - case 'qxSelect':
689   - iconType = 'icon-field-select';
690   - break;
691   - case 'qxMultiSelect':
692   - iconType = 'icon-field-multi-select';
693   - break;
694   - case 'qxMobile':
695   - iconType = 'icon-field-mobile';
696   - break;
697   - case 'qxMoney':
698   - iconType = 'icon-field-finance';
699   - break;
700   - case 'qxEmail':
701   - iconType = 'icon-field-email';
702   - break;
703   - case 'qxPercent':
704   - iconType = 'icon-field-percent';
705   - break;
706   - case 'qxUpload':
707   - iconType = 'icon-field-file';
708   - break;
709   - case 'qxUploadImage':
710   - iconType = 'icon-field-img';
711   - break;
712   - case 'qxAddress':
713   - iconType = 'icon-field-address';
714   - break;
715   - case 'qxRichText':
716   - iconType = 'icon-field-richtext';
717   - break;
718   - case 'qxLocation':
719   - iconType = 'icon-field-location';
720   - break;
721   - case 'orgSelector':
722   - iconType = 'icon-field-department';
723   - break;
724   - case 'userSelector':
725   - iconType = 'icon-field-user';
726   - break;
727   - case 'createdBy':
728   - case 'created_by':
729   - iconType = 'icon-field-created-by';
730   - break;
731   - case 'createdAt':
732   - case 'created_at':
733   - iconType = 'icon-field-created-at';
734   - break;
735   - case 'updatedBy':
736   - case 'updated_by':
737   - iconType = 'icon-field-updated-by';
738   - break;
739   - case 'updatedAt':
740   - case 'updated_at':
741   - iconType = 'icon-field-updated-at';
742   - break;
743   - case 'qxBizNo':
744   - iconType = 'icon-field-no';
745   - break;
746   - case 'relSelector':
747   - iconType = 'icon-field-rel';
748   - break;
749   - case 'relField':
750   - iconType = 'icon-field-ref';
751   - break;
752   - case 'subform':
753   - case 'table':
754   - iconType = 'icon-field-subform';
755   - break;
756   - case 'qxTree':
757   - iconType = 'icon-field-tree';
758   - break;
759   - case 'qxFormula':
760   - iconType = 'icon-field-formula';
761   - break;
762   - case 'qxDivider':
763   - iconType = 'icon-field-divider';
764   - break;
765   - case 'qxRemark':
766   - iconType = 'icon-field-remark';
767   - break;
768   - case 'qxEmbed':
769   - iconType = 'icon-field-embed ';
770   - break;
771   - case 'qxTabs':
772   - iconType = 'icon-editor_tab';
773   - break;
774   - case 'qxLayout':
775   - iconType = 'icon-editor_grid';
776   - break;
777   - case 'simple':
778   - iconType = 'icon-editor_layout';
779   - break;
780   - case 'tabC':
781   - iconType = 'icon-editor_tab';
782   - break;
783   - case 'layout':
784   - iconType = 'icon-editor_grid';
785   - break;
786   - default:
787   - iconType = 'icon-field-text';
788   - break;
789   - }
790   - return <QxBaseIcon type={iconType} />;
791   -};
792   -
793 675 export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
794 676 value,
795 677 field,
796 678 remove,
797 679 onChange,
798 680 mode = 'condition',
799   - ValueAssignmentPopup,
  681 + node,
  682 + nodes,
800 683 }) => {
801   - const [CustomValueRender, setCustomValueRender] =
802   - useState<() => React.ReactNode>();
803   -
  684 + const valuesObj = value?.valuesObj?.[0] || {};
804 685 const [open, setOpen] = useState(false);
805 686
806   - const handleChange = (val: any) => {
  687 + const handleChange = (val: any[]) => {
807 688 onChange?.({
808 689 ...(value || {}),
809   - mappingValues: val?.map((i: any) => i.value),
  690 + mappingValues: val?.length ? val?.map((i: any) => i.value) : [],
810 691 valuesObj: val,
811 692 });
812 693 };
... ... @@ -841,23 +722,23 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
841 722 onChange?.(newValue);
842 723 };
843 724
844   - const handleDefaultValueSettingChange = (
845   - val: any,
846   - valueRender: () => React.ReactNode,
847   - ) => {
848   - handleChange(val);
849   - setCustomValueRender(() => valueRender);
  725 + const handleAssignment = (value: string) => {
  726 + handleChange([{ type: 'FIELD', value }]);
  727 + setOpen(false);
850 728 };
851 729
852   - const handleOpenChange = setOpen;
  730 + const { genDisplayDom } = useNodeFieldDisplay({
  731 + node: node,
  732 + nodes: nodes,
  733 + limitTypes: [field.fieldType],
  734 + });
  735 +
  736 + const getName = (values: any[]) => {
  737 + return values.map((value) => genDisplayDom(value))
  738 + }
853 739
854 740 const RenderContent = (
855 741 <>
856   - {typeof CustomValueRender === 'function' && (
857   - <div className="qx-base-condition-item__content-value-render">
858   - {CustomValueRender()}
859   - </div>
860   - )}
861 742 <QxFieldSetter
862 743 value={value?.valuesObj}
863 744 fieldGroupType={value?.fieldGroupType}
... ... @@ -865,11 +746,12 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
865 746 isMultiple={multipleType.includes(value?.fieldGroupType)}
866 747 isRange={optValTypeCheck.isRangeType(value?.opt)}
867 748 disabled={optValTypeCheck.isEmptyType(value?.opt)}
  749 + getName={getName}
868 750 />
869   - {typeof ValueAssignmentPopup !== 'undefined' && (
  751 + {mode === 'variable' && (
870 752 <ControlOutlined
871 753 onClick={() => {
872   - handleOpenChange(!open);
  754 + setOpen(!open);
873 755 }}
874 756 className="qx-base-condition-item__content-suffix"
875 757 />
... ... @@ -900,7 +782,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
900 782 <div className="qx-base-condition-item__header">
901 783 <div className="qx-base-condition-item__header-left">
902 784 <span className="qx-base-condition-item__header-icon">
903   - <WidgetsIcon widgetName={field?.extract?.widget} />
  785 + <QxWidgetIcon widgetName={field?.extract?.widget} />
904 786 </span>
905 787 <span className="qx-base-condition-item__header-text">
906 788 {field.fieldName}
... ... @@ -921,16 +803,18 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
921 803 </div>
922 804 </div>
923 805 <div className="qx-base-condition-item__content">
924   - {typeof ValueAssignmentPopup !== 'undefined' ? (
925   - <ValueAssignmentPopup
  806 + {mode === 'variable' ? (
  807 + <QxFlowNodeFieldSelector
  808 + mode={mode}
  809 + node={node!}
  810 + nodes={nodes!}
926 811 open={open}
927   - value={value}
928   - field={field}
929   - onChange={handleDefaultValueSettingChange}
930   - onOpenChange={handleOpenChange}
  812 + value={valuesObj?.value}
  813 + onChange={handleAssignment}
  814 + limitTypes={[field.fieldType]}
931 815 >
932 816 {RenderContent}
933   - </ValueAssignmentPopup>
  817 + </QxFlowNodeFieldSelector>
934 818 ) : (
935 819 RenderContent
936 820 )}
... ... @@ -942,9 +826,10 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
942 826 export interface ValueAssignmentPopupProps
943 827 extends Omit<QxBaseConditionItemProps, 'onChange'> {
944 828 children?: React.ReactNode;
945   - onChange?: (val: any, valueRender: () => React.ReactNode) => void;
  829 + onChange?: (val: any) => void;
946 830 open?: boolean;
947 831 onOpenChange?: (open: boolean) => void;
  832 + onClear?: () => void;
948 833 }
949 834
950 835 export interface QxBaseConditionItemProps {
... ... @@ -952,6 +837,9 @@ export interface QxBaseConditionItemProps {
952 837 value?: any;
953 838 onChange?: (val: any) => void;
954 839 remove?: (field: QxBaseConditionField) => void;
955   - ValueAssignmentPopup?: React.FC<ValueAssignmentPopupProps>;
  840 + ValueAssignment?: React.FC<ValueAssignmentPopupProps>;
956 841 mode?: string;
  842 + node?: INode;
  843 + nodes?: INode[];
  844 + // customDisplay?: (val: string) => React.ReactNode;
957 845 }
... ...
1 1 import React, { useEffect, useState } from 'react';
2   -import type { ValueAssignmentPopupProps } from '../qx-base-condition-item';
3 2 import { QxBaseConditionItem } from '../qx-base-condition-item';
4 3 import './index.less';
  4 +import { INode } from '../qx-flow-node-selector';
5 5
6 6 export enum FieldBaseType {
7 7 STRING = 'TEXT',
... ... @@ -9,9 +9,8 @@ export enum FieldBaseType {
9 9 YEAR_SEC = 'DATE',
10 10 }
11 11
12   -
13 12 export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => {
14   - const [localOptions, setLocalOptions] = useState(props.value || props.options || []);
  13 + const [localOptions, setLocalOptions] = useState(props.options || []);
15 14
16 15 const getDefaultConditionOptions = (item: QxBaseConditionField) => ({
17 16 ...item,
... ... @@ -36,7 +35,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => {
36 35 const handleDelete = (key: number) => {
37 36 localOptions.splice(key, 1);
38 37 setLocalOptions([...localOptions]);
39   - props.onChange?.([...localOptions])
  38 + props.onChange?.([...localOptions]);
40 39 };
41 40
42 41 useEffect(() => {
... ... @@ -52,13 +51,16 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => {
52 51 key={item.code || key}
53 52 mode={props.mode}
54 53 {...item}
55   - value={props.value?.[key] || getDefaultConditionOptions(item)}
  54 + value={Object.assign(
  55 + {},
  56 + getDefaultConditionOptions(item),
  57 + props.value?.[key] || {},
  58 + )}
  59 + nodes={props.nodes}
  60 + node={props.node}
56 61 field={item}
57 62 onChange={(val) => handleItemChange(val, key)}
58 63 remove={() => handleDelete(key)}
59   - ValueAssignmentPopup={
60   - (item.showValueAssignmentPopup ?? true) ? props.ValueAssignmentPopup : undefined
61   - }
62 64 />
63 65 </div>
64 66 ));
... ... @@ -80,13 +82,13 @@ export interface QxBaseConditionField {
80 82 }
81 83
82 84 export interface QxBaseConditionOptionsProps extends QxBaseConditionField {
83   - showValueAssignmentPopup?: boolean;
  85 + showValueAssignment?: boolean;
84 86 isMultiple?: boolean;
85 87 isRange?: boolean;
86 88 [key: string]: any;
87 89 }
88 90
89   -export type QxBaseConditionMode = 'condition' | 'variable'
  91 +export type QxBaseConditionMode = 'condition' | 'variable';
90 92
91 93 export interface QxBaseConditionProps {
92 94 showIdx?: boolean;
... ... @@ -94,5 +96,6 @@ export interface QxBaseConditionProps {
94 96 options: QxBaseConditionOptionsProps[];
95 97 value: any[];
96 98 onChange?: (val: any[]) => void;
97   - ValueAssignmentPopup?: React.FC<ValueAssignmentPopupProps>;
  99 + nodes?: INode[]
  100 + node?: INode
98 101 }
... ...
... ... @@ -156,6 +156,7 @@ export interface paramColSelectProps extends ColSelectProps {
156 156 iconText?: string; // Popover-icon 自定义 后面跟随文本
157 157 allowClear?: boolean;
158 158 popupOnBody?: boolean; // 下拉 跟随 body 还是自身
  159 + getName?: (val: any) => void
159 160 }
160 161
161 162 export const QxFieldSetter: React.FC<paramColSelectProps> = ({
... ... @@ -355,7 +356,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({
355 356 * @param str 字符串形式
356 357 * @param joinParent 拼接父节点
357 358 */
358   - const getName = (val: string[], str?: boolean, joinParent?: boolean) => {
  359 + const getName = props?.getName || ((val: string[], str?: boolean, joinParent?: boolean) => {
359 360 const name: any[] = [];
360 361 let flag: boolean = false;
361 362 (colsTree || []).map((tree) => {
... ... @@ -440,7 +441,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({
440 441 } else {
441 442 return flag ? name : val;
442 443 }
443   - };
  444 + });
444 445
445 446 /**
446 447 * 名称转换
... ...
... ... @@ -41,7 +41,9 @@ export default () => {
41 41 hideCurrentOrg={true}
42 42 hideCurrentUser={true}
43 43 isMixValue={false}
44   - onChange={() => {}}
  44 + onChange={(val) => {
  45 + console.log(222222,val)
  46 + }}
45 47 tableFields={optionsList}
46 48 params={{ funCoded: 'cjQhMZnwkO2QoVzxVPC', useId: true }}
47 49 value={{
... ...
  1 +@import '~@qx/ui/src/style/variable.less';
  2 +
  3 +@ant-prefix-cls: ~'ant';
  4 +
  5 +.qx-node-select {
  6 + &-dropdown {
  7 + max-height: calc(8 * 32px);
  8 + overflow: auto;
  9 + background-color: white;
  10 + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
  11 + 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
  12 + border-radius: 8px;
  13 +
  14 + &-header {
  15 + display: flex;
  16 + align-items: center;
  17 + &__icon {
  18 + width: 14px;
  19 + height: 14px;
  20 + font-size: 14px;
  21 + line-height: 14px;
  22 + // color: @N7;
  23 + margin-right: 4px;
  24 + display: inline-block;
  25 + border-radius: 50%;
  26 + overflow: hidden;
  27 + // border: 1px solid @N6;
  28 + display: flex;
  29 + align-items: center;
  30 + justify-content: center;
  31 +
  32 + > img {
  33 + width: 14px;
  34 + height: 14px;
  35 + }
  36 + }
  37 + }
  38 +
  39 + .qx-node-select-item__icon {
  40 + margin-right: 4px;
  41 + }
  42 +
  43 + .@{ant-prefix-cls}-collapse-header,
  44 + .@{ant-prefix-cls}-collapse-content {
  45 + padding: 7px !important;
  46 + font-size: 14px;
  47 + }
  48 +
  49 + .@{ant-prefix-cls}-collapse-content {
  50 + background-color: @N3 !important;
  51 + padding: 0 !important;
  52 +
  53 + &-box {
  54 + padding: 0 !important;
  55 + }
  56 + }
  57 + }
  58 +
  59 + &-item {
  60 + width: 100%;
  61 + padding: 7px 16px 7px 32px !important;
  62 + &:hover {
  63 + background-color: @N4;
  64 + cursor: pointer;
  65 + }
  66 +
  67 + &__group {
  68 + padding: 0 0 0px 32px !important;
  69 +
  70 + &:hover {
  71 + background-color: transparent;
  72 + }
  73 + .@{ant-prefix-cls}-collapse-header {
  74 + padding: 7px 0 !important;
  75 + }
  76 + }
  77 + }
  78 +
  79 + &-input {
  80 + box-sizing: border-box;
  81 + margin: 0;
  82 + font-variant: tabular-nums;
  83 + list-style: none;
  84 + font-feature-settings: 'tnum', 'tnum';
  85 + position: relative;
  86 + display: inline-block;
  87 + width: 100%;
  88 + height: 32px;
  89 + min-width: 0;
  90 + padding: 4px 32px 4px 4px;
  91 + color: rgba(0, 0, 0, 0.85);
  92 + font-size: 14px;
  93 + line-height: 1.5715;
  94 + background-color: #fff;
  95 + background-image: none;
  96 + border: 1px solid #d9d9d9;
  97 + border-radius: 4px;
  98 + transition: all 0.3s;
  99 +
  100 + &:hover {
  101 + border-color: @B8;
  102 + }
  103 +
  104 + &__content {
  105 + display: inline-flex;
  106 + color: @N9;
  107 + font-size: 14px;
  108 +
  109 + &-item {
  110 + margin-right: 4px;
  111 + display: flex;
  112 + align-items: center;
  113 +
  114 + &__icon {
  115 + width: 14px;
  116 + height: 14px;
  117 + font-size: 14px;
  118 + line-height: 14px;
  119 + // color: @N7;
  120 + margin-right: 4px;
  121 + display: inline-block;
  122 + border-radius: 50%;
  123 + // overflow: hidden;
  124 + // border: 1px solid @N6;
  125 + display: flex;
  126 + align-items: center;
  127 + justify-content: center;
  128 +
  129 + > img {
  130 + width: inherit;
  131 + height: inherit;
  132 + }
  133 + }
  134 +
  135 + &__arrow {
  136 + display: inline-flex;
  137 + align-items: center;
  138 + color: @N6;
  139 + }
  140 + }
  141 + }
  142 +
  143 + &__suffix {
  144 + position: absolute;
  145 + right: 10px;
  146 + top: 50%;
  147 + transform: translateY(-50%);
  148 + }
  149 + }
  150 +}
  151 +
  152 +.qx-node-select-dropdown {
  153 + > .@{ant-prefix-cls}-collapse {
  154 + > .@{ant-prefix-cls}-collapse-item {
  155 + > .@{ant-prefix-cls}-collapse-content {
  156 + padding-right: 7px !important;
  157 + }
  158 + }
  159 + }
  160 +}
... ...
  1 +import { ControlOutlined } from '@ant-design/icons';
  2 +import { QxWidgetIcon } from '@qx/common';
  3 +import { Collapse, Dropdown, Empty, Tag } from 'antd';
  4 +import cls from 'classnames';
  5 +import { cloneDeep } from 'lodash-es';
  6 +import React, { useEffect, useMemo, useState } from 'react';
  7 +import { QxBaseIcon } from '../qx-base-icon';
  8 +import { request } from '../utils';
  9 +import './index.less';
  10 +// import type { FiledType } from '@/interface';
  11 +// import type { INode } from '@qx/flow';
  12 +
  13 +const getAppsFields = (params: string[], appId = 'default') => {
  14 + return request.post(`/qx-apaas-lowcode/app/${appId}/fields`, {
  15 + data: params,
  16 + });
  17 +};
  18 +
  19 +export const getNodesMap = (
  20 + nodes: INode[],
  21 + map: Record<string, INode> = {},
  22 +) => {
  23 + if (!nodes) return {};
  24 + for (let i = 0; i < nodes.length; i++) {
  25 + const node = nodes[i];
  26 + if (!map[node.id]) {
  27 + map[node.id] = node;
  28 + }
  29 +
  30 + if (node.children && Array.isArray(node.children)) {
  31 + getNodesMap(node.children, map);
  32 + }
  33 + }
  34 +
  35 + return map;
  36 +};
  37 +
  38 +export const getParentNodes = (
  39 + node?: INode,
  40 + treeNodes?: INode[],
  41 + parentNode: INode[] = [],
  42 + allNodes = getNodesMap(treeNodes!),
  43 +) => {
  44 + if (!node || !treeNodes) return [];
  45 + if (node.previousId) {
  46 + parentNode.push(allNodes[node.previousId]);
  47 + getParentNodes(allNodes[node.previousId], treeNodes, parentNode, allNodes);
  48 + }
  49 +
  50 + return parentNode;
  51 +};
  52 +
  53 +export enum FileTypeMap {
  54 + 'STRING' = '文本',
  55 + 'NUMBER' = '数字',
  56 + 'BOOL' = '布尔',
  57 + 'TIME' = '日期',
  58 + 'OBJECT' = '对象',
  59 + 'ARRAY' = '数组',
  60 + 'FORM' = '表单',
  61 + 'USER' = '人员',
  62 + 'ORG' = '部门',
  63 + 'FILE' = '文件',
  64 + 'PIC' = '图片',
  65 +}
  66 +
  67 +interface NodeFieldDisPlay {
  68 + node?: INode;
  69 + nodes?: INode[];
  70 + limitTypes?: string[];
  71 +}
  72 +
  73 +const icon = (icon: any) => {
  74 + if (icon?.$$typeof) {
  75 + return icon;
  76 + }
  77 + if (typeof icon !== 'string' && icon) {
  78 + return <QxBaseIcon type={icon.props.type} />;
  79 + }
  80 +
  81 + return <img src={icon} />;
  82 +};
  83 +
  84 +export const useNodeFieldDisplay = ({
  85 + node,
  86 + nodes,
  87 + limitTypes,
  88 +}: NodeFieldDisPlay) => {
  89 + const sourceParentNodes =
  90 + getParentNodes(node, nodes).filter(
  91 + (node) => !['default_DF_BRANCH'].includes(node.type),
  92 + ) || [];
  93 +
  94 + const optionalNodes = useMemo(() => {
  95 + const targetParentNodes = cloneDeep(sourceParentNodes);
  96 + if (!limitTypes) return targetParentNodes;
  97 +
  98 + function getEffectiveNodes(nodes: INode[]) {
  99 + return nodes.reduce<INode[]>((pre, cur) => {
  100 + const curNode = cur;
  101 +
  102 + if (Array.isArray(curNode.data?.result)) {
  103 + const resultNodes = (curNode.data.result as FiledType[]).filter(
  104 + (i) => {
  105 + if (i.child && Array.isArray(i.child)) {
  106 + // eslint-disable-next-line @typescript-eslint/no-use-before-define
  107 + i.child = getEffectiveResult(i.child);
  108 + }
  109 + return limitTypes!.includes(i.type);
  110 + },
  111 + );
  112 +
  113 + if (resultNodes?.length) {
  114 + curNode.data.result = resultNodes;
  115 + pre.push(curNode);
  116 + }
  117 + }
  118 +
  119 + return pre;
  120 + }, []);
  121 + }
  122 +
  123 + function getEffectiveResult(result: FiledType[]) {
  124 + return result.filter((item) => {
  125 + if (item.child && Array.isArray(item.child)) {
  126 + item.child = getEffectiveResult(item.child);
  127 + }
  128 + return limitTypes?.includes(item.fieldType);
  129 + });
  130 + }
  131 +
  132 + return getEffectiveNodes(targetParentNodes);
  133 + }, [limitTypes, sourceParentNodes]);
  134 +
  135 + const getId = (val?: string) => {
  136 + if (!val) return;
  137 + const startIndex = val.indexOf('|') + 1;
  138 + const endIndex = val.indexOf('}');
  139 + if (startIndex < endIndex) {
  140 + return val.substring(startIndex, endIndex);
  141 + }
  142 + return null;
  143 + };
  144 +
  145 + const genDisplayDom = (value: string) => {
  146 + const itemId = getId(value);
  147 + if (!itemId) return;
  148 + let displayConfig: any[] = [];
  149 + let n = true;
  150 + let index = 0;
  151 + while (n && index <= 20) {
  152 + displayConfig = [];
  153 + const curNode = optionalNodes[index] || {};
  154 + displayConfig.push({
  155 + title: curNode.name,
  156 + icon: curNode.icon,
  157 + ...(optionalNodes[index] || {}),
  158 + });
  159 + // eslint-disable-next-line @typescript-eslint/no-use-before-define
  160 + recursionNodeResult(curNode.data?.result);
  161 + index++;
  162 + }
  163 +
  164 + function recursionNodeResult(result: FiledType[], idx = 1) {
  165 + if (Array.isArray(result)) {
  166 + for (let i = 0; i < result.length; i++) {
  167 + const item = result[i];
  168 + if (!n) return;
  169 +
  170 + displayConfig.splice(idx, 1, item);
  171 + displayConfig = displayConfig.slice(0, idx + 1);
  172 +
  173 + if (itemId === item.id) {
  174 + n = false;
  175 + return;
  176 + }
  177 +
  178 + if (Array.isArray(item.child)) {
  179 + recursionNodeResult(item.child, idx + 1);
  180 + }
  181 + }
  182 + } else {
  183 + n = false;
  184 + }
  185 + }
  186 +
  187 + return (
  188 + <>
  189 + {displayConfig?.map((item, idx) => (
  190 + <div
  191 + key={idx}
  192 + className='qx-node-select-input__content-item'
  193 + >
  194 + {item.icon && (
  195 + <span
  196 + className='qx-node-select-input__content-item__icon'
  197 + >
  198 + {icon(item.icon)}
  199 + </span>
  200 + )}
  201 + <span
  202 + className='qx-node-select-input__content-item__text'
  203 + >
  204 + {item.type &&
  205 + FileTypeMap[item.type] &&
  206 + !item.icon &&
  207 + `[${FileTypeMap[item.type]}]`}
  208 + {item.title}
  209 + </span>
  210 + {idx !== displayConfig.length - 1 && (
  211 + <span
  212 + className='qx-node-select-input__content-item__arrow'
  213 + >
  214 + <svg
  215 + xmlns="http://www.w3.org/2000/svg"
  216 + width="1em"
  217 + height="1em"
  218 + fill="currentColor"
  219 + >
  220 + <path d="M5.80469 13.6423L4.86188 12.6995L9.57592 7.98548L4.86188 3.27143L5.80469 2.32863L10.9901 7.51403C10.9901 7.51406 10.9901 7.51407 10.5187 7.98548L10.9901 7.51403L11.4615 7.98548L5.80469 13.6423Z" />
  221 + </svg>
  222 + </span>
  223 + )}
  224 + </div>
  225 + ))}
  226 + </>
  227 + );
  228 + };
  229 +
  230 + return {
  231 + genDisplayDom,
  232 + optionalNodes,
  233 + };
  234 +};
  235 +
  236 +const SelectItem = (props: any) => {
  237 + if (props.type === 'FORM') {
  238 + return (
  239 + <div className={cls('qx-node-select-item', 'qx-node-select-item__group')}>
  240 + <Collapse
  241 + ghost
  242 + expandIconPosition="end"
  243 + items={[
  244 + {
  245 + label: (
  246 + <span>
  247 + {/* @ts-ignore */}
  248 + {`[${FileTypeMap[props.type]}]`}
  249 + {props.title}
  250 + </span>
  251 + ),
  252 + key: props.id,
  253 + children: props.child?.map((item: any, idx: number) => {
  254 + return (
  255 + <SelectItem
  256 + key={item.code || idx}
  257 + {...item}
  258 + onClick={props.onClick}
  259 + />
  260 + );
  261 + }),
  262 + },
  263 + ]}
  264 + />
  265 + </div>
  266 + );
  267 + }
  268 +
  269 + if (!props.child) {
  270 + return (
  271 + <div
  272 + className={cls('qx-node-select-item')}
  273 + onClick={() => props.onClick(props)}
  274 + >
  275 + {/* @ts-ignore */}
  276 + {props.icon ? props.icon : `[${FileTypeMap[props.type]}]`}
  277 + {props.title}
  278 + </div>
  279 + );
  280 + }
  281 +
  282 + return (
  283 + <div className={cls('qx-node-select-item', 'qx-node-select-item__group')}>
  284 + <Collapse
  285 + ghost
  286 + expandIconPosition="end"
  287 + items={[
  288 + {
  289 + label: (
  290 + <span>
  291 + {/* @ts-ignore */}
  292 + {`[${FileTypeMap[props.type]}]`}
  293 + {props.title}
  294 + </span>
  295 + ),
  296 + key: props.id,
  297 + children: props.child?.map((item: any, idx: number) => {
  298 + return (
  299 + <SelectItem
  300 + key={item.code || idx}
  301 + {...item}
  302 + onClick={props.onClick}
  303 + />
  304 + );
  305 + }),
  306 + },
  307 + ]}
  308 + />
  309 + </div>
  310 + );
  311 +};
  312 +
  313 +export const QxFlowNodeFieldSelector: React.FC<NodeFieldSelectProps> = (
  314 + props,
  315 +) => {
  316 + const { mode = 'select' } = props;
  317 +
  318 + const [visible, setVisible] = useState(false);
  319 +
  320 + const [inputDisplay, setInputDisplay] = useState<React.ReactNode>();
  321 +
  322 + const { optionalNodes, genDisplayDom } = useNodeFieldDisplay(props);
  323 +
  324 + const getOptions = () => {
  325 + if (!optionalNodes.length) {
  326 + return <Empty />;
  327 + }
  328 + return optionalNodes.map((node) => ({
  329 + label: (
  330 + <div className={cls('qx-node-select-dropdown-header')}>
  331 + <span className={cls('qx-node-select-dropdown-header__icon')}>
  332 + {icon(node.icon)}
  333 + </span>
  334 + {node.name}
  335 + </div>
  336 + ),
  337 + key: node.id,
  338 + children: Array.isArray(node.data?.result)
  339 + ? (node.data.result as FiledType[])?.map((item) => {
  340 + return (
  341 + <SelectItem
  342 + key={item.id}
  343 + {...item}
  344 + onClick={(value: FiledType) => {
  345 + // eslint-disable-next-line @typescript-eslint/no-use-before-define
  346 + handleItemClick(node.id, value || item);
  347 + }}
  348 + />
  349 + );
  350 + })
  351 + : [],
  352 + }));
  353 + };
  354 +
  355 + const getForms = () => {
  356 + const forms: FiledType[] = [];
  357 + optionalNodes.forEach((node) => {
  358 + if (Array.isArray(node.data?.result)) {
  359 + // eslint-disable-next-line @typescript-eslint/no-use-before-define
  360 + recursionNodeResult(node.data?.result);
  361 + }
  362 + });
  363 +
  364 + function recursionNodeResult(result: FiledType[] | FiledType) {
  365 + if (!Array.isArray(result) && result) {
  366 + if (result.qxProps && result.qxProps?.formId) {
  367 + forms.push(result);
  368 + }
  369 + }
  370 + if (Array.isArray(result)) {
  371 + result.forEach((i) => {
  372 + if (i.qxProps && i.qxProps?.formId) {
  373 + forms.push(i);
  374 + } else if (i.child) {
  375 + recursionNodeResult(i.child);
  376 + }
  377 + });
  378 + }
  379 + }
  380 +
  381 + return forms;
  382 + };
  383 +
  384 + const renderInputDisplay = (val: string = props.value || '') => {
  385 + setInputDisplay(
  386 + <Tag bordered={false} className="qx-node-select-input__content">
  387 + {genDisplayDom(val)}
  388 + </Tag>,
  389 + );
  390 + };
  391 +
  392 + const handleGetAppsFields = async () => {
  393 + const forms = getForms();
  394 + const ids = forms.map(
  395 + (item) => item.qxProps?.formId && !props.limitTypes?.includes(item.type),
  396 + );
  397 + console.log(ids, 'ids')
  398 + if (Array.isArray(ids) && ids.length) {
  399 + try {
  400 + const data = await getAppsFields(ids as any[]);
  401 + Object.keys(data).forEach((id) => {
  402 + forms.forEach((i) => {
  403 + if (i.qxProps?.formId === id) {
  404 + if (Array.isArray(i.child)) {
  405 + i.child.push(
  406 + data[id].map((item: FiledType) => ({
  407 + icon: (
  408 + <span className="qx-node-select-item__icon">
  409 + <QxWidgetIcon
  410 + widgetName={item.extract?.widget || 'qxInput'}
  411 + />
  412 + </span>
  413 + ),
  414 + title: item.name,
  415 + code: item.code,
  416 + id: item.code,
  417 + type: item.extract?.fieldType,
  418 + })),
  419 + );
  420 + } else {
  421 + i.child = data[id].map((item: FiledType) => ({
  422 + icon: (
  423 + <span className="qx-node-select-item__icon">
  424 + <QxWidgetIcon
  425 + widgetName={item.extract?.widget || 'qxInput'}
  426 + />
  427 + </span>
  428 + ),
  429 + title: item.name,
  430 + code: item.code,
  431 + id: item.code,
  432 + type: item.extract?.fieldType,
  433 + }));
  434 + }
  435 + }
  436 + });
  437 + });
  438 + renderInputDisplay();
  439 + } catch (error) {
  440 + // appsFields = {};
  441 + }
  442 + }
  443 + };
  444 +
  445 + const handleItemClick = (nodeKey: string, item: any) => {
  446 + const newValue = '${' + `${nodeKey}|${item.id}` + '}';
  447 + props.onChange?.(newValue, item);
  448 + if (!props.children) {
  449 + renderInputDisplay(newValue);
  450 + }
  451 + setVisible(false);
  452 + };
  453 +
  454 + useEffect(() => {
  455 + handleGetAppsFields();
  456 + }, []);
  457 +
  458 + useEffect(() => {
  459 + setVisible(props.open ?? false);
  460 + }, [props.open]);
  461 +
  462 + return (
  463 + <div className={cls('qx-node-select')}>
  464 + <Dropdown
  465 + trigger={['click']}
  466 + overlayStyle={{ width: `${props?.width}px` }}
  467 + overlayClassName={cls('qx-node-select-dropdown')}
  468 + open={visible}
  469 + dropdownRender={() => (
  470 + <Collapse ghost expandIconPosition="end" items={getOptions()} />
  471 + )}
  472 + onOpenChange={(open) => {
  473 + if (mode === 'select') {
  474 + setVisible(open);
  475 + }
  476 + }}
  477 + >
  478 + {props.children ? (
  479 + props.children
  480 + ) : (
  481 + <div className={cls('qx-node-select-input')}>
  482 + {inputDisplay}
  483 + {mode === 'variable' && (
  484 + <span
  485 + className="qx-node-select-input__suffix"
  486 + onClick={() => setVisible(!visible)}
  487 + >
  488 + <ControlOutlined />
  489 + </span>
  490 + )}
  491 + </div>
  492 + )}
  493 + </Dropdown>
  494 + </div>
  495 + );
  496 +};
  497 +
  498 +export interface NodeFieldSelectProps {
  499 + node: INode;
  500 + nodes: INode[];
  501 + onChange?: (val: any, opt?: FiledType) => void;
  502 + value?: string;
  503 + children?: React.ReactNode;
  504 + limitTypes?: string[];
  505 + width?: number;
  506 + mode?: 'select' | 'variable';
  507 + open?: boolean;
  508 +}
  509 +
  510 +export interface FiledType {
  511 + type: string;
  512 + id: string;
  513 + title: string;
  514 + code: string;
  515 + valueOpt?: string;
  516 + mappingValues?: any[];
  517 + detailValues?: any;
  518 + condition?: any;
  519 + child?: FiledType[];
  520 + qxProps?: {
  521 + formId?: string;
  522 + };
  523 + [key: string]: any;
  524 +}
  525 +
  526 +export interface INode {
  527 + id: string;
  528 + type: string;
  529 + name: string;
  530 + data?: any;
  531 + children?: INode[];
  532 + path?: string[];
  533 + configuring?: boolean;
  534 + validateStatusError?: boolean;
  535 + next?: string[];
  536 + tools?: string[] | React.FC<any>[];
  537 + debuggerError?: boolean;
  538 + debuggerSuccess?: boolean;
  539 + [key: string]: any;
  540 +}
... ...
  1 +---
  2 +nav:
  3 + path: /component
  4 + title: 组件
  5 + order: 1
  6 +group:
  7 + path: /common
  8 + title: 表单字段图标
  9 + order: 0
  10 +---
  11 +
  12 +## QxWidgetIcon 表单字段图标
  13 +
  14 +### 表单字段图标
  15 +
  16 +```tsx
  17 +import { QxWidgetIcon } from '@qx/common';
  18 +
  19 +export default () => {
  20 + return <QxWidgetIcon widgetName="qxInput" />;
  21 +};
  22 +```
... ...
  1 +import { QxBaseIcon } from '../qx-base-icon';
  2 +
  3 +export const QxWidgetIcon = ({ widgetName }: { widgetName: string }) => {
  4 + let iconType: string = '';
  5 + switch (widgetName) {
  6 + case 'qxInput':
  7 + iconType = 'icon-field-text';
  8 + break;
  9 + case 'qxNumber':
  10 + iconType = 'icon-field-num';
  11 + break;
  12 + case 'dateTime':
  13 + case 'qxDatetime':
  14 + iconType = 'icon-field-datetime';
  15 + break;
  16 + case 'qxTime':
  17 + iconType = 'icon-field-time';
  18 + break;
  19 + case 'qxSwitch':
  20 + iconType = 'icon-field-boolean';
  21 + break;
  22 + case 'qxSelect':
  23 + iconType = 'icon-field-select';
  24 + break;
  25 + case 'qxMultiSelect':
  26 + iconType = 'icon-field-multi-select';
  27 + break;
  28 + case 'qxMobile':
  29 + iconType = 'icon-field-mobile';
  30 + break;
  31 + case 'qxMoney':
  32 + iconType = 'icon-field-finance';
  33 + break;
  34 + case 'qxEmail':
  35 + iconType = 'icon-field-email';
  36 + break;
  37 + case 'qxPercent':
  38 + iconType = 'icon-field-percent';
  39 + break;
  40 + case 'qxUpload':
  41 + iconType = 'icon-field-file';
  42 + break;
  43 + case 'qxUploadImage':
  44 + iconType = 'icon-field-img';
  45 + break;
  46 + case 'qxAddress':
  47 + iconType = 'icon-field-address';
  48 + break;
  49 + case 'qxRichText':
  50 + iconType = 'icon-field-richtext';
  51 + break;
  52 + case 'qxLocation':
  53 + iconType = 'icon-field-location';
  54 + break;
  55 + case 'orgSelector':
  56 + iconType = 'icon-field-department';
  57 + break;
  58 + case 'userSelector':
  59 + iconType = 'icon-field-user';
  60 + break;
  61 + case 'createdBy':
  62 + case 'created_by':
  63 + iconType = 'icon-field-created-by';
  64 + break;
  65 + case 'createdAt':
  66 + case 'created_at':
  67 + iconType = 'icon-field-created-at';
  68 + break;
  69 + case 'updatedBy':
  70 + case 'updated_by':
  71 + iconType = 'icon-field-updated-by';
  72 + break;
  73 + case 'updatedAt':
  74 + case 'updated_at':
  75 + iconType = 'icon-field-updated-at';
  76 + break;
  77 + case 'qxBizNo':
  78 + iconType = 'icon-field-no';
  79 + break;
  80 + case 'relSelector':
  81 + iconType = 'icon-field-rel';
  82 + break;
  83 + case 'relField':
  84 + iconType = 'icon-field-ref';
  85 + break;
  86 + case 'subform':
  87 + case 'table':
  88 + iconType = 'icon-field-subform';
  89 + break;
  90 + case 'qxTree':
  91 + iconType = 'icon-field-tree';
  92 + break;
  93 + case 'qxFormula':
  94 + iconType = 'icon-field-formula';
  95 + break;
  96 + case 'qxDivider':
  97 + iconType = 'icon-field-divider';
  98 + break;
  99 + case 'qxRemark':
  100 + iconType = 'icon-field-remark';
  101 + break;
  102 + case 'qxEmbed':
  103 + iconType = 'icon-field-embed ';
  104 + break;
  105 + case 'qxTabs':
  106 + iconType = 'icon-editor_tab';
  107 + break;
  108 + case 'qxLayout':
  109 + iconType = 'icon-editor_grid';
  110 + break;
  111 + case 'simple':
  112 + iconType = 'icon-editor_layout';
  113 + break;
  114 + case 'tabC':
  115 + iconType = 'icon-editor_tab';
  116 + break;
  117 + case 'layout':
  118 + iconType = 'icon-editor_grid';
  119 + break;
  120 + default:
  121 + iconType = 'icon-field-text';
  122 + break;
  123 + }
  124 + return <QxBaseIcon type={iconType} />;
  125 +};
... ...