Showing
9 changed files
with
351 additions
and
27 deletions
@@ -20,5 +20,6 @@ export * from './qx-dynamic-component'; | @@ -20,5 +20,6 @@ export * from './qx-dynamic-component'; | ||
20 | export * from './qx-widget-icon'; | 20 | export * from './qx-widget-icon'; |
21 | export * from './qx-flow-node-selector'; | 21 | export * from './qx-flow-node-selector'; |
22 | export * from './qx-icon-selector'; | 22 | export * from './qx-icon-selector'; |
23 | +export * from './qx-condition'; | ||
23 | 24 | ||
24 | 25 |
@@ -684,6 +684,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | @@ -684,6 +684,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | ||
684 | subset, | 684 | subset, |
685 | params, | 685 | params, |
686 | fieldGroupType, | 686 | fieldGroupType, |
687 | + showAssignment = true, | ||
687 | isMixValue, | 688 | isMixValue, |
688 | }) => { | 689 | }) => { |
689 | const valuesObj = value?.valuesObj?.[0] || {}; | 690 | const valuesObj = value?.valuesObj?.[0] || {}; |
@@ -760,12 +761,15 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | @@ -760,12 +761,15 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | ||
760 | widget={field.extract?.widget} | 761 | widget={field.extract?.widget} |
761 | isMixValue={isMixValue} | 762 | isMixValue={isMixValue} |
762 | /> | 763 | /> |
763 | - <ControlOutlined | ||
764 | - onClick={() => { | ||
765 | - setOpen(!open); | ||
766 | - }} | ||
767 | - className="qx-base-condition-item__content-suffix" | ||
768 | - /> | 764 | + |
765 | + {showAssignment ? ( | ||
766 | + <ControlOutlined | ||
767 | + onClick={() => { | ||
768 | + setOpen(!open); | ||
769 | + }} | ||
770 | + className="qx-base-condition-item__content-suffix" | ||
771 | + /> | ||
772 | + ) : null} | ||
769 | </> | 773 | </> |
770 | ); | 774 | ); |
771 | 775 | ||
@@ -817,18 +821,22 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | @@ -817,18 +821,22 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ | ||
817 | </div> | 821 | </div> |
818 | </div> | 822 | </div> |
819 | <div className="qx-base-condition-item__content"> | 823 | <div className="qx-base-condition-item__content"> |
820 | - <QxFlowNodeFieldSelector | ||
821 | - mode="variable" | ||
822 | - node={node!} | ||
823 | - nodes={nodes!} | ||
824 | - open={open} | ||
825 | - value={valuesObj?.value} | ||
826 | - onChange={handleAssignment} | ||
827 | - limitTypes={[field.extract?.fieldType]} | ||
828 | - subset={subset} | ||
829 | - > | ||
830 | - {RenderContent} | ||
831 | - </QxFlowNodeFieldSelector> | 824 | + {showAssignment ? ( |
825 | + <QxFlowNodeFieldSelector | ||
826 | + mode="variable" | ||
827 | + node={node!} | ||
828 | + nodes={nodes!} | ||
829 | + open={open} | ||
830 | + value={valuesObj?.value} | ||
831 | + onChange={handleAssignment} | ||
832 | + limitTypes={[field.extract?.fieldType]} | ||
833 | + subset={subset} | ||
834 | + > | ||
835 | + {RenderContent} | ||
836 | + </QxFlowNodeFieldSelector> | ||
837 | + ) : ( | ||
838 | + RenderContent | ||
839 | + )} | ||
832 | </div> | 840 | </div> |
833 | </div> | 841 | </div> |
834 | ); | 842 | ); |
@@ -848,7 +856,7 @@ export interface QxBaseConditionItemProps { | @@ -848,7 +856,7 @@ export interface QxBaseConditionItemProps { | ||
848 | value?: any; | 856 | value?: any; |
849 | onChange?: (val: any) => void; | 857 | onChange?: (val: any) => void; |
850 | remove?: (field: QxBaseConditionField) => void; | 858 | remove?: (field: QxBaseConditionField) => void; |
851 | - ValueAssignment?: React.FC<ValueAssignmentPopupProps>; | 859 | + showAssignment?: boolean; |
852 | mode?: string; | 860 | mode?: string; |
853 | node?: INode; | 861 | node?: INode; |
854 | nodes?: INode[]; | 862 | nodes?: INode[]; |
@@ -3,7 +3,7 @@ import { QxBaseConditionItem } from '../qx-base-condition-item'; | @@ -3,7 +3,7 @@ import { QxBaseConditionItem } from '../qx-base-condition-item'; | ||
3 | import { INode } from '../qx-flow-node-selector'; | 3 | import { INode } from '../qx-flow-node-selector'; |
4 | import './index.less'; | 4 | import './index.less'; |
5 | 5 | ||
6 | -const FieldBaseType = { | 6 | +export const FieldBaseType = { |
7 | STRING: 'TEXT', | 7 | STRING: 'TEXT', |
8 | DOUBLE: 'NUM', | 8 | DOUBLE: 'NUM', |
9 | NUMBER: 'NUM', | 9 | NUMBER: 'NUM', |
@@ -68,6 +68,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { | @@ -68,6 +68,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { | ||
68 | nodes={props.nodes} | 68 | nodes={props.nodes} |
69 | node={props.node} | 69 | node={props.node} |
70 | subset={props.subset} | 70 | subset={props.subset} |
71 | + showAssignment={props.showAssignment} | ||
71 | onChange={(val) => handleItemChange(val, key)} | 72 | onChange={(val) => handleItemChange(val, key)} |
72 | remove={() => handleDelete(key)} | 73 | remove={() => handleDelete(key)} |
73 | /> | 74 | /> |
@@ -107,13 +108,21 @@ export interface QxBaseConditionOptionsProps extends QxBaseConditionField { | @@ -107,13 +108,21 @@ export interface QxBaseConditionOptionsProps extends QxBaseConditionField { | ||
107 | 108 | ||
108 | export type QxBaseConditionMode = 'condition' | 'variable'; | 109 | export type QxBaseConditionMode = 'condition' | 'variable'; |
109 | 110 | ||
111 | +export interface QxBaseConditionValueType { | ||
112 | + code: string; | ||
113 | + type: string; | ||
114 | + opt: string; | ||
115 | + mappingValues: string[]; | ||
116 | + [key: string]: any | ||
117 | +} | ||
118 | + | ||
110 | export interface QxBaseConditionProps { | 119 | export interface QxBaseConditionProps { |
111 | showIdx?: boolean; | 120 | showIdx?: boolean; |
112 | mode: QxBaseConditionMode; | 121 | mode: QxBaseConditionMode; |
113 | - options: QxBaseConditionOptionsProps[]; | ||
114 | - value: any[]; | 122 | + value?: QxBaseConditionValueType[]; |
115 | onChange?: (val: any[]) => void; | 123 | onChange?: (val: any[]) => void; |
116 | nodes?: INode[]; | 124 | nodes?: INode[]; |
117 | node?: INode; | 125 | node?: INode; |
118 | subset?: boolean; | 126 | subset?: boolean; |
127 | + showAssignment?: boolean; | ||
119 | } | 128 | } |
src/qx-condition-sql/index.less
0 → 100644
1 | +@import '~@qx/ui/src/style/variable.less'; | ||
2 | +@prefix: ~'qx-condition-sql'; | ||
3 | + | ||
4 | +.@{prefix} { | ||
5 | + | ||
6 | + &__header { | ||
7 | + display: flex; | ||
8 | + align-items: center; | ||
9 | + justify-content: space-between; | ||
10 | + border-top: 1px solid @N4; | ||
11 | + } | ||
12 | + | ||
13 | + &__title { | ||
14 | + color: @N9; | ||
15 | + } | ||
16 | + | ||
17 | + &__select { | ||
18 | + color: @B8; | ||
19 | + width: 60px !important; | ||
20 | + text-align: right; | ||
21 | + :global { | ||
22 | + .ant-select-suffix { | ||
23 | + color: @B8; | ||
24 | + } | ||
25 | + } | ||
26 | + } | ||
27 | + | ||
28 | + &__input-textarea { | ||
29 | + display: block; | ||
30 | + } | ||
31 | +} |
src/qx-condition-sql/index.tsx
0 → 100644
1 | +import { FunctionOutlined } from '@ant-design/icons'; | ||
2 | +import { Input, Select } from 'antd'; | ||
3 | +import cx from 'classnames'; | ||
4 | +import React, { useEffect, useState } from 'react'; | ||
5 | +import './index.less'; | ||
6 | + | ||
7 | +const prefix = 'qx-condition-sql'; | ||
8 | + | ||
9 | +/** | ||
10 | + * 条件表达式运算符 | ||
11 | + */ | ||
12 | +export const OperatorCol = [ | ||
13 | + { | ||
14 | + key: 'AND', | ||
15 | + text: '且', | ||
16 | + }, | ||
17 | + { | ||
18 | + key: 'OR', | ||
19 | + text: '或', | ||
20 | + }, | ||
21 | + { | ||
22 | + key: 'CUSTOM', | ||
23 | + text: 'CUSTOM', | ||
24 | + }, | ||
25 | +]; | ||
26 | + | ||
27 | +const options = OperatorCol.map((item) => ({ | ||
28 | + value: item.key, | ||
29 | + label: item.key === 'CUSTOM' ? <FunctionOutlined /> : item.text, | ||
30 | +})); | ||
31 | + | ||
32 | +/** | ||
33 | + * 组合条件 | ||
34 | + */ | ||
35 | +export const QxConditionSql: React.FC<ConditionSqlProps> = ({ | ||
36 | + onChange, | ||
37 | + onBlur, | ||
38 | + value, | ||
39 | +}) => { | ||
40 | + const [state, setState] = useState<ConditionSqlState>( | ||
41 | + value || { | ||
42 | + sqlType: 'AND', | ||
43 | + }, | ||
44 | + ); | ||
45 | + | ||
46 | + const handleInputValueChange = ( | ||
47 | + e: React.ChangeEvent<HTMLTextAreaElement>, | ||
48 | + ) => { | ||
49 | + const expression = e.target.value; | ||
50 | + setState({ ...state, expression }); | ||
51 | + }; | ||
52 | + | ||
53 | + useEffect(() => { | ||
54 | + onChange?.(state); | ||
55 | + }, [state]); | ||
56 | + | ||
57 | + useEffect(() => { | ||
58 | + onChange?.({ expression: '', sqlType: 'AND' }); | ||
59 | + }, []); | ||
60 | + | ||
61 | + return ( | ||
62 | + <div className={prefix}> | ||
63 | + <div className={`${prefix}__header`}> | ||
64 | + <span className={`${prefix}__title`}>添加组合方式</span> | ||
65 | + <Select | ||
66 | + value={state.sqlType || value?.sqlType} | ||
67 | + bordered={false} | ||
68 | + className={`${prefix}__select`} | ||
69 | + options={options} | ||
70 | + onChange={(value) => { | ||
71 | + setState({ | ||
72 | + sqlType: value, | ||
73 | + expression: | ||
74 | + value !== 'CUSTOM' | ||
75 | + ? `1 ${value.toLocaleLowerCase()} 2` | ||
76 | + : state.expression, | ||
77 | + }); | ||
78 | + }} | ||
79 | + /> | ||
80 | + </div> | ||
81 | + {state.sqlType === 'CUSTOM' ? ( | ||
82 | + <div className={`${prefix}__input`}> | ||
83 | + <Input.TextArea | ||
84 | + className={cx(`${prefix}__input-textarea`, 'qx-input')} | ||
85 | + defaultValue={state.expression} | ||
86 | + onChange={handleInputValueChange} | ||
87 | + onBlur={(e) => { | ||
88 | + handleInputValueChange(e); | ||
89 | + onBlur?.({ ...state, expression: e.target.value }); | ||
90 | + }} | ||
91 | + placeholder="请使用序号、小括号(英文)、逻辑符(and,or) 编写表达式例如:(1 and 2) or 3" | ||
92 | + /> | ||
93 | + </div> | ||
94 | + ) : null} | ||
95 | + </div> | ||
96 | + ); | ||
97 | +}; | ||
98 | + | ||
99 | +interface ConditionSqlProps { | ||
100 | + onChange?: (val: ConditionSqlState) => void; | ||
101 | + onBlur?: (val: ConditionSqlState) => void; | ||
102 | + value?: ConditionSqlState; | ||
103 | +} | ||
104 | + | ||
105 | +export interface ConditionSqlState { | ||
106 | + expression?: string; | ||
107 | + sqlType: SqlType; | ||
108 | +} | ||
109 | + | ||
110 | +export type SqlType = 'AND' | 'OR' | 'CUSTOM'; |
src/qx-condition/index.less
0 → 100644
src/qx-condition/index.md
0 → 100644
1 | +--- | ||
2 | +nav: | ||
3 | + path: /component | ||
4 | + title: 组件 | ||
5 | + order: 1 | ||
6 | +group: | ||
7 | + path: /common | ||
8 | + title: 条件配置 | ||
9 | + order: 0 | ||
10 | +--- | ||
11 | + | ||
12 | +## QxBaseCondition 条件配置 | ||
13 | + | ||
14 | +### 条件配置 | ||
15 | + | ||
16 | +```tsx | ||
17 | +import { QxCondition } from '@qx/common'; | ||
18 | +import { Form } from 'antd'; | ||
19 | + | ||
20 | +export default () => { | ||
21 | + return ( | ||
22 | + <div> | ||
23 | + <Form | ||
24 | + initialValues={{ | ||
25 | + condition: { | ||
26 | + sqlType: 'AND', | ||
27 | + operators: [ | ||
28 | + { | ||
29 | + type: 'STRING', | ||
30 | + name: '文本', | ||
31 | + mappingValues: [], | ||
32 | + field: { | ||
33 | + code: 'text', | ||
34 | + name: '文本', | ||
35 | + extract: { widget: 'qxInput', fieldType: 'STRING' }, | ||
36 | + }, | ||
37 | + }, | ||
38 | + { | ||
39 | + type: 'STRING', | ||
40 | + name: '文本', | ||
41 | + mappingValues: [], | ||
42 | + field: { | ||
43 | + code: 'text', | ||
44 | + name: '文本', | ||
45 | + extract: { widget: 'qxInput', fieldType: 'STRING' }, | ||
46 | + }, | ||
47 | + }, | ||
48 | + ], | ||
49 | + }, | ||
50 | + }} | ||
51 | + onValuesChange={(value, values) => { | ||
52 | + console.log('values', values) | ||
53 | + }} | ||
54 | + > | ||
55 | + <Form.Item name="condition"> | ||
56 | + <QxCondition showAssignment={false} /> | ||
57 | + </Form.Item> | ||
58 | + </Form> | ||
59 | + </div> | ||
60 | + ); | ||
61 | +}; | ||
62 | +``` | ||
63 | + | ||
64 | +<API id="QxCondition"></API> |
src/qx-condition/index.tsx
0 → 100644
1 | +import React, { useEffect } from 'react'; | ||
2 | +import { | ||
3 | + QxBaseCondition, | ||
4 | + QxBaseConditionValueType, | ||
5 | +} from '../qx-base-condition'; | ||
6 | +import { QxConditionSql, SqlType } from '../qx-condition-sql'; | ||
7 | +import { INode } from '../qx-flow-node-selector'; | ||
8 | + | ||
9 | +export const QxCondition: React.FC<QxConditionProps> = ({ | ||
10 | + value, | ||
11 | + className, | ||
12 | + style, | ||
13 | + enabled = true, | ||
14 | + showAssignment = true, | ||
15 | + onChange, | ||
16 | + node, | ||
17 | + nodes | ||
18 | +}) => { | ||
19 | + const handleChange = (opt: Partial<QxConditionValueType>) => { | ||
20 | + onChange?.(Object.assign({}, value, opt, { enabled })); | ||
21 | + }; | ||
22 | + | ||
23 | + return ( | ||
24 | + <div className={`qx-condition ${className}`} style={style}> | ||
25 | + <QxBaseCondition | ||
26 | + mode="condition" | ||
27 | + nodes={nodes} | ||
28 | + node={node} | ||
29 | + showAssignment={showAssignment} | ||
30 | + value={value?.operators} | ||
31 | + onChange={(condition) => { | ||
32 | + handleChange({ | ||
33 | + operators: condition, | ||
34 | + }); | ||
35 | + }} | ||
36 | + /> | ||
37 | + | ||
38 | + {(value?.operators?.length || 0) >= 2 ? ( | ||
39 | + <QxConditionSql value={value} onChange={handleChange} /> | ||
40 | + ) : null} | ||
41 | + </div> | ||
42 | + ); | ||
43 | +}; | ||
44 | + | ||
45 | +export interface QxConditionValueType { | ||
46 | + /** | ||
47 | + * 是否启用条件 | ||
48 | + */ | ||
49 | + enabled?: boolean; | ||
50 | + /** | ||
51 | + * 条件组合类型 | ||
52 | + */ | ||
53 | + sqlType: SqlType; | ||
54 | + /** | ||
55 | + * 自定义条件表达式 | ||
56 | + */ | ||
57 | + expression: string; | ||
58 | + /** | ||
59 | + * 算子 | ||
60 | + */ | ||
61 | + operators: OperatorsType[]; | ||
62 | +} | ||
63 | + | ||
64 | +export type OperatorsType = QxBaseConditionValueType; | ||
65 | + | ||
66 | +export interface QxConditionProps { | ||
67 | + [key: string]: any; | ||
68 | + value?: QxConditionValueType; | ||
69 | + onChange?: (value: QxConditionValueType) => void; | ||
70 | + className?: string; | ||
71 | + style?: React.CSSProperties; | ||
72 | + node?: INode | ||
73 | + nodes?: INode[] | ||
74 | + showAssignment?: boolean | ||
75 | +} |
@@ -95,7 +95,6 @@ export const useNodeFieldDisplay = ({ | @@ -95,7 +95,6 @@ export const useNodeFieldDisplay = ({ | ||
95 | ) || [], | 95 | ) || [], |
96 | ); | 96 | ); |
97 | 97 | ||
98 | - // const [targetParentNodes, setTargetParentNodes] = useState(sourceParentNodes) | ||
99 | const [inputDisplay, setInputDisplay] = useState<React.ReactNode>(); | 98 | const [inputDisplay, setInputDisplay] = useState<React.ReactNode>(); |
100 | const [optionalNodes, setOptionalNodes] = useState<INode[]>([]); // 根据 fieldType 过滤后的 nodes | 99 | const [optionalNodes, setOptionalNodes] = useState<INode[]>([]); // 根据 fieldType 过滤后的 nodes |
101 | 100 | ||
@@ -109,9 +108,9 @@ export const useNodeFieldDisplay = ({ | @@ -109,9 +108,9 @@ export const useNodeFieldDisplay = ({ | ||
109 | return null; | 108 | return null; |
110 | }; | 109 | }; |
111 | 110 | ||
112 | - const genDisplayDom = (value: string, nodes: INode[]) => { | 111 | + const getDisplayConfig = (value: string, nodes: INode[] = optionalNodes) => { |
113 | const itemId = getId(value); | 112 | const itemId = getId(value); |
114 | - if (!itemId) return; | 113 | + if (!itemId) return []; |
115 | let displayConfig: any[] = []; | 114 | let displayConfig: any[] = []; |
116 | let n = true; | 115 | let n = true; |
117 | let index = 0; | 116 | let index = 0; |
@@ -151,6 +150,12 @@ export const useNodeFieldDisplay = ({ | @@ -151,6 +150,12 @@ export const useNodeFieldDisplay = ({ | ||
151 | n = false; | 150 | n = false; |
152 | } | 151 | } |
153 | } | 152 | } |
153 | + | ||
154 | + return displayConfig | ||
155 | + } | ||
156 | + | ||
157 | + const genDisplayDom = (value: string, nodes: INode[] = optionalNodes) => { | ||
158 | + const displayConfig = getDisplayConfig(value, nodes) | ||
154 | return ( | 159 | return ( |
155 | <> | 160 | <> |
156 | {displayConfig?.map((item, idx) => ( | 161 | {displayConfig?.map((item, idx) => ( |
@@ -185,7 +190,10 @@ export const useNodeFieldDisplay = ({ | @@ -185,7 +190,10 @@ export const useNodeFieldDisplay = ({ | ||
185 | ); | 190 | ); |
186 | }; | 191 | }; |
187 | 192 | ||
188 | - const renderInputDisplay = (nodes: INode[] = optionalNodes, val: string = value || '', ) => { | 193 | + const renderInputDisplay = ( |
194 | + nodes: INode[] = optionalNodes, | ||
195 | + val: string = value || '', | ||
196 | + ) => { | ||
189 | setInputDisplay( | 197 | setInputDisplay( |
190 | <Tag bordered={false} className="qx-node-select-input__content"> | 198 | <Tag bordered={false} className="qx-node-select-input__content"> |
191 | {genDisplayDom(val, nodes)} | 199 | {genDisplayDom(val, nodes)} |
@@ -345,8 +353,8 @@ export const useNodeFieldDisplay = ({ | @@ -345,8 +353,8 @@ export const useNodeFieldDisplay = ({ | ||
345 | return { | 353 | return { |
346 | genDisplayDom, | 354 | genDisplayDom, |
347 | optionalNodes, | 355 | optionalNodes, |
348 | - // targetParentNodes, | ||
349 | renderInputDisplay, | 356 | renderInputDisplay, |
357 | + getDisplayConfig, | ||
350 | inputDisplay, | 358 | inputDisplay, |
351 | }; | 359 | }; |
352 | }; | 360 | }; |