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 | }; |