Commit 07984f0266d2e64bd33cd9b46b30b1fbe09f6a88

Authored by qiang.tian
1 parent 801ce943

feat: condition

@@ -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 }
  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 +}
  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';
  1 +@import '~@qx/ui/src/style/variable.less';
  2 +
  3 +.qx-condition {
  4 + width: 100%;
  5 +
  6 + &-item {
  7 + display: flex;
  8 + align-items: center;
  9 +
  10 +
  11 + &__idx {
  12 + color: @N9;
  13 + margin-right: 4px;
  14 + width: 24px;
  15 + display: inline-block;
  16 + }
  17 + }
  18 +}
  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>
  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 };