Showing
9 changed files
with
351 additions
and
27 deletions
| ... | ... | @@ -684,6 +684,7 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ |
| 684 | 684 | subset, |
| 685 | 685 | params, |
| 686 | 686 | fieldGroupType, |
| 687 | + showAssignment = true, | |
| 687 | 688 | isMixValue, |
| 688 | 689 | }) => { |
| 689 | 690 | const valuesObj = value?.valuesObj?.[0] || {}; |
| ... | ... | @@ -760,12 +761,15 @@ export const QxBaseConditionItem: React.FC<QxBaseConditionItemProps> = ({ |
| 760 | 761 | widget={field.extract?.widget} |
| 761 | 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 | 821 | </div> |
| 818 | 822 | </div> |
| 819 | 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 | 840 | </div> |
| 833 | 841 | </div> |
| 834 | 842 | ); |
| ... | ... | @@ -848,7 +856,7 @@ export interface QxBaseConditionItemProps { |
| 848 | 856 | value?: any; |
| 849 | 857 | onChange?: (val: any) => void; |
| 850 | 858 | remove?: (field: QxBaseConditionField) => void; |
| 851 | - ValueAssignment?: React.FC<ValueAssignmentPopupProps>; | |
| 859 | + showAssignment?: boolean; | |
| 852 | 860 | mode?: string; |
| 853 | 861 | node?: INode; |
| 854 | 862 | nodes?: INode[]; | ... | ... |
| ... | ... | @@ -3,7 +3,7 @@ import { QxBaseConditionItem } from '../qx-base-condition-item'; |
| 3 | 3 | import { INode } from '../qx-flow-node-selector'; |
| 4 | 4 | import './index.less'; |
| 5 | 5 | |
| 6 | -const FieldBaseType = { | |
| 6 | +export const FieldBaseType = { | |
| 7 | 7 | STRING: 'TEXT', |
| 8 | 8 | DOUBLE: 'NUM', |
| 9 | 9 | NUMBER: 'NUM', |
| ... | ... | @@ -68,6 +68,7 @@ export const QxBaseCondition: React.FC<QxBaseConditionProps> = (props) => { |
| 68 | 68 | nodes={props.nodes} |
| 69 | 69 | node={props.node} |
| 70 | 70 | subset={props.subset} |
| 71 | + showAssignment={props.showAssignment} | |
| 71 | 72 | onChange={(val) => handleItemChange(val, key)} |
| 72 | 73 | remove={() => handleDelete(key)} |
| 73 | 74 | /> |
| ... | ... | @@ -107,13 +108,21 @@ export interface QxBaseConditionOptionsProps extends QxBaseConditionField { |
| 107 | 108 | |
| 108 | 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 | 119 | export interface QxBaseConditionProps { |
| 111 | 120 | showIdx?: boolean; |
| 112 | 121 | mode: QxBaseConditionMode; |
| 113 | - options: QxBaseConditionOptionsProps[]; | |
| 114 | - value: any[]; | |
| 122 | + value?: QxBaseConditionValueType[]; | |
| 115 | 123 | onChange?: (val: any[]) => void; |
| 116 | 124 | nodes?: INode[]; |
| 117 | 125 | node?: INode; |
| 118 | 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 | 95 | ) || [], |
| 96 | 96 | ); |
| 97 | 97 | |
| 98 | - // const [targetParentNodes, setTargetParentNodes] = useState(sourceParentNodes) | |
| 99 | 98 | const [inputDisplay, setInputDisplay] = useState<React.ReactNode>(); |
| 100 | 99 | const [optionalNodes, setOptionalNodes] = useState<INode[]>([]); // 根据 fieldType 过滤后的 nodes |
| 101 | 100 | |
| ... | ... | @@ -109,9 +108,9 @@ export const useNodeFieldDisplay = ({ |
| 109 | 108 | return null; |
| 110 | 109 | }; |
| 111 | 110 | |
| 112 | - const genDisplayDom = (value: string, nodes: INode[]) => { | |
| 111 | + const getDisplayConfig = (value: string, nodes: INode[] = optionalNodes) => { | |
| 113 | 112 | const itemId = getId(value); |
| 114 | - if (!itemId) return; | |
| 113 | + if (!itemId) return []; | |
| 115 | 114 | let displayConfig: any[] = []; |
| 116 | 115 | let n = true; |
| 117 | 116 | let index = 0; |
| ... | ... | @@ -151,6 +150,12 @@ export const useNodeFieldDisplay = ({ |
| 151 | 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 | 159 | return ( |
| 155 | 160 | <> |
| 156 | 161 | {displayConfig?.map((item, idx) => ( |
| ... | ... | @@ -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 | 197 | setInputDisplay( |
| 190 | 198 | <Tag bordered={false} className="qx-node-select-input__content"> |
| 191 | 199 | {genDisplayDom(val, nodes)} |
| ... | ... | @@ -345,8 +353,8 @@ export const useNodeFieldDisplay = ({ |
| 345 | 353 | return { |
| 346 | 354 | genDisplayDom, |
| 347 | 355 | optionalNodes, |
| 348 | - // targetParentNodes, | |
| 349 | 356 | renderInputDisplay, |
| 357 | + getDisplayConfig, | |
| 350 | 358 | inputDisplay, |
| 351 | 359 | }; |
| 352 | 360 | }; | ... | ... |