Commit 04129d565215a68f370e114fd2b5491455fccd46

Authored by 陈洋
2 parents 32b245be 2122a03e

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

1 { 1 {
2 "name": "@qx/common", 2 "name": "@qx/common",
3 - "version": "3.0.0-alpha.19", 3 + "version": "3.0.0-alpha.21",
4 "description": "A react library developed with dumi", 4 "description": "A react library developed with dumi",
5 "license": "MIT", 5 "license": "MIT",
6 "module": "dist/index.js", 6 "module": "dist/index.js",
@@ -15,5 +15,7 @@ export * from './qx-btn'; @@ -15,5 +15,7 @@ export * from './qx-btn';
15 export * from './qx-progress'; 15 export * from './qx-progress';
16 export * from './qx-search-input'; 16 export * from './qx-search-input';
17 export * from './qx-dynamic-component'; 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,16 +53,10 @@
53 color: @N7; 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 import { ControlOutlined } from '@ant-design/icons'; 1 import { ControlOutlined } from '@ant-design/icons';
2 -import { Select, Tooltip, Input } from 'antd'; 2 +import { Select, Tooltip } from 'antd';
3 import { size } from 'lodash-es'; 3 import { size } from 'lodash-es';
4 import React, { useState } from 'react'; 4 import React, { useState } from 'react';
5 import type { QxBaseConditionField } from '../qx-base-condition'; 5 import type { QxBaseConditionField } from '../qx-base-condition';
6 import { QxBaseIcon } from '../qx-base-icon'; 6 import { QxBaseIcon } from '../qx-base-icon';
7 import { QxFieldSetter } from '../qx-field-setter'; 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 import './index.less'; 15 import './index.less';
10 16
@@ -666,147 +672,22 @@ const optValTypeCheck = { @@ -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 export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ 675 export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
794 value, 676 value,
795 field, 677 field,
796 remove, 678 remove,
797 onChange, 679 onChange,
798 mode = 'condition', 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 const [open, setOpen] = useState(false); 685 const [open, setOpen] = useState(false);
805 686
806 - const handleChange = (val: any) => { 687 + const handleChange = (val: any[]) => {
807 onChange?.({ 688 onChange?.({
808 ...(value || {}), 689 ...(value || {}),
809 - mappingValues: val?.map((i: any) => i.value), 690 + mappingValues: val?.length ? val?.map((i: any) => i.value) : [],
810 valuesObj: val, 691 valuesObj: val,
811 }); 692 });
812 }; 693 };
@@ -841,23 +722,23 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ @@ -841,23 +722,23 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
841 onChange?.(newValue); 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 const RenderContent = ( 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 <QxFieldSetter 742 <QxFieldSetter
862 value={value?.valuesObj} 743 value={value?.valuesObj}
863 fieldGroupType={value?.fieldGroupType} 744 fieldGroupType={value?.fieldGroupType}
@@ -865,11 +746,12 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ @@ -865,11 +746,12 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
865 isMultiple={multipleType.includes(value?.fieldGroupType)} 746 isMultiple={multipleType.includes(value?.fieldGroupType)}
866 isRange={optValTypeCheck.isRangeType(value?.opt)} 747 isRange={optValTypeCheck.isRangeType(value?.opt)}
867 disabled={optValTypeCheck.isEmptyType(value?.opt)} 748 disabled={optValTypeCheck.isEmptyType(value?.opt)}
  749 + getName={getName}
868 /> 750 />
869 - {typeof ValueAssignmentPopup !== 'undefined' && ( 751 + {mode === 'variable' && (
870 <ControlOutlined 752 <ControlOutlined
871 onClick={() => { 753 onClick={() => {
872 - handleOpenChange(!open); 754 + setOpen(!open);
873 }} 755 }}
874 className="qx-base-condition-item__content-suffix" 756 className="qx-base-condition-item__content-suffix"
875 /> 757 />
@@ -900,7 +782,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ @@ -900,7 +782,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
900 <div className="qx-base-condition-item__header"> 782 <div className="qx-base-condition-item__header">
901 <div className="qx-base-condition-item__header-left"> 783 <div className="qx-base-condition-item__header-left">
902 <span className="qx-base-condition-item__header-icon"> 784 <span className="qx-base-condition-item__header-icon">
903 - <WidgetsIcon widgetName={field?.extract?.widget} /> 785 + <QxWidgetIcon widgetName={field?.extract?.widget} />
904 </span> 786 </span>
905 <span className="qx-base-condition-item__header-text"> 787 <span className="qx-base-condition-item__header-text">
906 {field.fieldName} 788 {field.fieldName}
@@ -921,16 +803,18 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ @@ -921,16 +803,18 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
921 </div> 803 </div>
922 </div> 804 </div>
923 <div className="qx-base-condition-item__content"> 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 open={open} 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 {RenderContent} 816 {RenderContent}
933 - </ValueAssignmentPopup> 817 + </QxFlowNodeFieldSelector>
934 ) : ( 818 ) : (
935 RenderContent 819 RenderContent
936 )} 820 )}
@@ -942,9 +826,10 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ @@ -942,9 +826,10 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({
942 export interface ValueAssignmentPopupProps 826 export interface ValueAssignmentPopupProps
943 extends Omit<QxBaseConditionItemProps, 'onChange'> { 827 extends Omit<QxBaseConditionItemProps, 'onChange'> {
944 children?: React.ReactNode; 828 children?: React.ReactNode;
945 - onChange?: (val: any, valueRender: () => React.ReactNode) => void; 829 + onChange?: (val: any) => void;
946 open?: boolean; 830 open?: boolean;
947 onOpenChange?: (open: boolean) => void; 831 onOpenChange?: (open: boolean) => void;
  832 + onClear?: () => void;
948 } 833 }
949 834
950 export interface QxBaseConditionItemProps { 835 export interface QxBaseConditionItemProps {
@@ -952,6 +837,9 @@ export interface QxBaseConditionItemProps { @@ -952,6 +837,9 @@ export interface QxBaseConditionItemProps {
952 value?: any; 837 value?: any;
953 onChange?: (val: any) => void; 838 onChange?: (val: any) => void;
954 remove?: (field: QxBaseConditionField) => void; 839 remove?: (field: QxBaseConditionField) => void;
955 - ValueAssignmentPopup?: React.FC<ValueAssignmentPopupProps>; 840 + ValueAssignment?: React.FC<ValueAssignmentPopupProps>;
956 mode?: string; 841 mode?: string;
  842 + node?: INode;
  843 + nodes?: INode[];
  844 + // customDisplay?: (val: string) => React.ReactNode;
957 } 845 }
1 import React, { useEffect, useState } from 'react'; 1 import React, { useEffect, useState } from 'react';
2 -import type { ValueAssignmentPopupProps } from '../qx-base-condition-item';  
3 import { QxBaseConditionItem } from '../qx-base-condition-item'; 2 import { QxBaseConditionItem } from '../qx-base-condition-item';
4 import './index.less'; 3 import './index.less';
  4 +import { INode } from '../qx-flow-node-selector';
5 5
6 export enum FieldBaseType { 6 export enum FieldBaseType {
7 STRING = 'TEXT', 7 STRING = 'TEXT',
@@ -9,9 +9,8 @@ export enum FieldBaseType { @@ -9,9 +9,8 @@ export enum FieldBaseType {
9 YEAR_SEC = 'DATE', 9 YEAR_SEC = 'DATE',
10 } 10 }
11 11
12 -  
13 export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { 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 const getDefaultConditionOptions = (item: QxBaseConditionField) => ({ 15 const getDefaultConditionOptions = (item: QxBaseConditionField) => ({
17 ...item, 16 ...item,
@@ -36,7 +35,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { @@ -36,7 +35,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => {
36 const handleDelete = (key: number) => { 35 const handleDelete = (key: number) => {
37 localOptions.splice(key, 1); 36 localOptions.splice(key, 1);
38 setLocalOptions([...localOptions]); 37 setLocalOptions([...localOptions]);
39 - props.onChange?.([...localOptions]) 38 + props.onChange?.([...localOptions]);
40 }; 39 };
41 40
42 useEffect(() => { 41 useEffect(() => {
@@ -52,13 +51,16 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { @@ -52,13 +51,16 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => {
52 key={item.code || key} 51 key={item.code || key}
53 mode={props.mode} 52 mode={props.mode}
54 {...item} 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 field={item} 61 field={item}
57 onChange={(val) => handleItemChange(val, key)} 62 onChange={(val) => handleItemChange(val, key)}
58 remove={() => handleDelete(key)} 63 remove={() => handleDelete(key)}
59 - ValueAssignmentPopup={  
60 - (item.showValueAssignmentPopup ?? true) ? props.ValueAssignmentPopup : undefined  
61 - }  
62 /> 64 />
63 </div> 65 </div>
64 )); 66 ));
@@ -80,13 +82,13 @@ export interface QxBaseConditionField { @@ -80,13 +82,13 @@ export interface QxBaseConditionField {
80 } 82 }
81 83
82 export interface QxBaseConditionOptionsProps extends QxBaseConditionField { 84 export interface QxBaseConditionOptionsProps extends QxBaseConditionField {
83 - showValueAssignmentPopup?: boolean; 85 + showValueAssignment?: boolean;
84 isMultiple?: boolean; 86 isMultiple?: boolean;
85 isRange?: boolean; 87 isRange?: boolean;
86 [key: string]: any; 88 [key: string]: any;
87 } 89 }
88 90
89 -export type QxBaseConditionMode = 'condition' | 'variable' 91 +export type QxBaseConditionMode = 'condition' | 'variable';
90 92
91 export interface QxBaseConditionProps { 93 export interface QxBaseConditionProps {
92 showIdx?: boolean; 94 showIdx?: boolean;
@@ -94,5 +96,6 @@ export interface QxBaseConditionProps { @@ -94,5 +96,6 @@ export interface QxBaseConditionProps {
94 options: QxBaseConditionOptionsProps[]; 96 options: QxBaseConditionOptionsProps[];
95 value: any[]; 97 value: any[];
96 onChange?: (val: any[]) => void; 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,6 +156,7 @@ export interface paramColSelectProps extends ColSelectProps {
156 iconText?: string; // Popover-icon 自定义 后面跟随文本 156 iconText?: string; // Popover-icon 自定义 后面跟随文本
157 allowClear?: boolean; 157 allowClear?: boolean;
158 popupOnBody?: boolean; // 下拉 跟随 body 还是自身 158 popupOnBody?: boolean; // 下拉 跟随 body 还是自身
  159 + getName?: (val: any) => void
159 } 160 }
160 161
161 export const QxFieldSetter: React.FC<paramColSelectProps> = ({ 162 export const QxFieldSetter: React.FC<paramColSelectProps> = ({
@@ -355,7 +356,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ @@ -355,7 +356,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({
355 * @param str 字符串形式 356 * @param str 字符串形式
356 * @param joinParent 拼接父节点 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 const name: any[] = []; 360 const name: any[] = [];
360 let flag: boolean = false; 361 let flag: boolean = false;
361 (colsTree || []).map((tree) => { 362 (colsTree || []).map((tree) => {
@@ -440,7 +441,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({ @@ -440,7 +441,7 @@ export const QxFieldSetter: React.FC<paramColSelectProps> = ({
440 } else { 441 } else {
441 return flag ? name : val; 442 return flag ? name : val;
442 } 443 }
443 - }; 444 + });
444 445
445 /** 446 /**
446 * 名称转换 447 * 名称转换
@@ -41,7 +41,9 @@ export default () => { @@ -41,7 +41,9 @@ export default () => {
41 hideCurrentOrg={true} 41 hideCurrentOrg={true}
42 hideCurrentUser={true} 42 hideCurrentUser={true}
43 isMixValue={false} 43 isMixValue={false}
44 - onChange={() => {}} 44 + onChange={(val) => {
  45 + console.log(222222,val)
  46 + }}
45 tableFields={optionsList} 47 tableFields={optionsList}
46 params={{ funCoded: 'cjQhMZnwkO2QoVzxVPC', useId: true }} 48 params={{ funCoded: 'cjQhMZnwkO2QoVzxVPC', useId: true }}
47 value={{ 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 +};