半成品
This commit is contained in:
parent
031f748bf2
commit
8901b57389
|
@ -1,17 +1,22 @@
|
||||||
.#{$ns}CBGroup {
|
.#{$ns}CBGroup {
|
||||||
|
font-size: $fontSizeSm;
|
||||||
|
|
||||||
&-toolbar {
|
&-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-field {
|
&-field,
|
||||||
|
&-operator {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: px2rem(120px);
|
min-width: px2rem(120px);
|
||||||
|
margin-left: $gap-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-fieldCaret {
|
&-fieldCaret,
|
||||||
|
&-operatorCaret {
|
||||||
transition: transform 0.3s ease-out;
|
transition: transform 0.3s ease-out;
|
||||||
margin: 0 $gap-xs;
|
margin: 0 $gap-xs;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -27,7 +32,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-fieldInput.is-active &-fieldCaret {
|
&-fieldInput.is-active &-fieldCaret,
|
||||||
|
&-operatorInput.is-active &-operatorCaret {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +103,7 @@
|
||||||
.#{$ns}CBInputSwitch {
|
.#{$ns}CBInputSwitch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 5px;
|
margin-left: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
> a {
|
> a {
|
||||||
@include icon-color();
|
@include icon-color();
|
||||||
|
@ -108,3 +114,29 @@
|
||||||
height: px2rem(10px);
|
height: px2rem(10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$ns}CBFunc {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&-select {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-args {
|
||||||
|
display: inline-block;
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 5px;
|
||||||
|
color: $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,156 @@
|
||||||
import {ExpressionComplex, Field, Funcs} from './types';
|
import {ExpressionComplex, Field, Funcs, Func, ExpressionFunc} from './types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ConditionField from './Field';
|
||||||
|
import {autobind, findTree} from '../../utils/helper';
|
||||||
|
import Value from './Value';
|
||||||
|
import InputSwitch from './InputSwitch';
|
||||||
|
import ConditionFunc from './Func';
|
||||||
|
import {ThemeProps, themeable} from '../../theme';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支持4中表达式设置方式
|
* 支持4中表达式设置方式
|
||||||
|
*
|
||||||
* 1. 直接就是值,由用户直接填写。
|
* 1. 直接就是值,由用户直接填写。
|
||||||
* 2. 选择字段,让用户选一个字段。
|
* 2. 选择字段,让用户选一个字段。
|
||||||
* 3. 选择一个函数,然后会参数里面的输入情况是个递归。
|
* 3. 选择一个函数,然后会参数里面的输入情况是个递归。
|
||||||
* 4. 粗暴点,函数让用户自己书写。
|
* 4. 粗暴点,函数让用户自己书写。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ExpressionProps {
|
export interface ExpressionProps extends ThemeProps {
|
||||||
value: ExpressionComplex;
|
value: ExpressionComplex;
|
||||||
onChange: (value: ExpressionComplex) => void;
|
index?: number;
|
||||||
|
onChange: (value: ExpressionComplex, index?: number) => void;
|
||||||
valueField?: Field;
|
valueField?: Field;
|
||||||
fields?: Field[];
|
fields?: Field[];
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
|
defaultType?: 'value' | 'field' | 'func' | 'raw';
|
||||||
allowedTypes?: Array<'value' | 'field' | 'func' | 'raw'>;
|
allowedTypes?: Array<'value' | 'field' | 'func' | 'raw'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Expression extends React.Component<ExpressionProps> {}
|
const fieldMap = {
|
||||||
|
value: '值',
|
||||||
|
field: '字段',
|
||||||
|
func: '函数',
|
||||||
|
raw: '公式'
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Expression extends React.Component<ExpressionProps> {
|
||||||
|
@autobind
|
||||||
|
handleInputTypeChange(type: 'value' | 'field' | 'func' | 'raw') {
|
||||||
|
let value = this.props.value;
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
|
||||||
|
if (type === 'value') {
|
||||||
|
value = '';
|
||||||
|
} else if (type === 'func') {
|
||||||
|
value = {
|
||||||
|
type: 'func',
|
||||||
|
func: (findTree(this.props.funcs!, item => (item as Func).type) as Func)
|
||||||
|
?.type,
|
||||||
|
args: []
|
||||||
|
};
|
||||||
|
} else if (type === 'field') {
|
||||||
|
value = {
|
||||||
|
type: 'field',
|
||||||
|
field: ''
|
||||||
|
};
|
||||||
|
} else if (type === 'raw') {
|
||||||
|
value = {
|
||||||
|
type: 'raw',
|
||||||
|
value: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleValueChange(data: any) {
|
||||||
|
this.props.onChange(data, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleFieldChange(field: string) {
|
||||||
|
let value = this.props.value;
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
value = {
|
||||||
|
type: 'field',
|
||||||
|
field
|
||||||
|
};
|
||||||
|
onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleFuncChange(func: any) {
|
||||||
|
let value = this.props.value;
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
value = {
|
||||||
|
...func,
|
||||||
|
type: 'func'
|
||||||
|
};
|
||||||
|
onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleRawChange() {}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {value, defaultType, allowedTypes, funcs, fields} = this.props;
|
||||||
|
const inputType =
|
||||||
|
((value as any)?.type === 'field'
|
||||||
|
? 'field'
|
||||||
|
: (value as any)?.type === 'func'
|
||||||
|
? 'func'
|
||||||
|
: (value as any)?.type === 'raw'
|
||||||
|
? 'raw'
|
||||||
|
: value !== undefined
|
||||||
|
? 'value'
|
||||||
|
: undefined) ||
|
||||||
|
defaultType ||
|
||||||
|
allowedTypes?.[0] ||
|
||||||
|
'value';
|
||||||
|
|
||||||
|
const types = allowedTypes || ['value', 'field', 'func'];
|
||||||
|
|
||||||
|
if ((!Array.isArray(funcs) || !funcs.length) && ~types.indexOf('func')) {
|
||||||
|
types.splice(types.indexOf('func'), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{types.length > 1 ? (
|
||||||
|
<InputSwitch
|
||||||
|
value={inputType}
|
||||||
|
onChange={this.handleInputTypeChange}
|
||||||
|
options={types.map(item => ({
|
||||||
|
label: fieldMap[item],
|
||||||
|
value: item
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{inputType === 'value' ? <Value /> : null}
|
||||||
|
|
||||||
|
{inputType === 'field' ? (
|
||||||
|
<ConditionField
|
||||||
|
value={(value as any)?.field}
|
||||||
|
onChange={this.handleFieldChange}
|
||||||
|
options={fields!}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{inputType === 'func' ? (
|
||||||
|
<ConditionFunc
|
||||||
|
value={value as ExpressionFunc}
|
||||||
|
onChange={this.handleFuncChange}
|
||||||
|
funcs={funcs}
|
||||||
|
fields={fields}
|
||||||
|
defaultType={defaultType}
|
||||||
|
allowedTypes={allowedTypes}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default themeable(Expression);
|
||||||
|
|
|
@ -2,21 +2,20 @@ import React from 'react';
|
||||||
import PopOverContainer from '../PopOverContainer';
|
import PopOverContainer from '../PopOverContainer';
|
||||||
import ListRadios from '../ListRadios';
|
import ListRadios from '../ListRadios';
|
||||||
import ResultBox from '../ResultBox';
|
import ResultBox from '../ResultBox';
|
||||||
import {ClassNamesFn} from '../../theme';
|
import {ClassNamesFn, ThemeProps, themeable} from '../../theme';
|
||||||
import {Icon} from '../icons';
|
import {Icon} from '../icons';
|
||||||
import {find} from 'lodash';
|
import {find} from 'lodash';
|
||||||
import {findTree, noop} from '../../utils/helper';
|
import {findTree, noop} from '../../utils/helper';
|
||||||
|
|
||||||
export interface ConditionFieldProps {
|
export interface ConditionFieldProps extends ThemeProps {
|
||||||
options: Array<any>;
|
options: Array<any>;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
classnames: ClassNamesFn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const option2value = (item: any) => item.name;
|
const option2value = (item: any) => item.name;
|
||||||
|
|
||||||
export default function ConditionField({
|
export function ConditionField({
|
||||||
options,
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
|
@ -57,3 +56,5 @@ export default function ConditionField({
|
||||||
</PopOverContainer>
|
</PopOverContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default themeable(ConditionField);
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Func, ExpressionFunc, Field, Funcs} from './types';
|
||||||
|
import {ThemeProps, themeable} from '../../theme';
|
||||||
|
import PopOverContainer from '../PopOverContainer';
|
||||||
|
import ListRadios from '../ListRadios';
|
||||||
|
import {autobind, findTree, noop} from '../../utils/helper';
|
||||||
|
import ResultBox from '../ResultBox';
|
||||||
|
import {Icon} from '../icons';
|
||||||
|
import Expression from './Expression';
|
||||||
|
|
||||||
|
export interface ConditionFuncProps extends ThemeProps {
|
||||||
|
value: ExpressionFunc;
|
||||||
|
onChange: (value: ExpressionFunc) => void;
|
||||||
|
fields?: Field[];
|
||||||
|
funcs?: Funcs;
|
||||||
|
defaultType?: 'value' | 'field' | 'func' | 'raw';
|
||||||
|
allowedTypes?: Array<'value' | 'field' | 'func' | 'raw'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option2value = (item: Func) => item.type;
|
||||||
|
|
||||||
|
export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||||
|
@autobind
|
||||||
|
handleFuncChange(type: string) {
|
||||||
|
const value = {...this.props.value};
|
||||||
|
value.func = type;
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleArgChange(arg: any, index: number) {
|
||||||
|
const value = {...this.props.value};
|
||||||
|
value.args = Array.isArray(value.args) ? value.args.concat() : [];
|
||||||
|
value.args.splice(index, 1, arg);
|
||||||
|
this.props.onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFunc(func: Func) {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
fields,
|
||||||
|
value,
|
||||||
|
funcs,
|
||||||
|
defaultType,
|
||||||
|
allowedTypes
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('CBFunc-args')}>
|
||||||
|
<span>(</span>
|
||||||
|
{Array.isArray(func.args) && func.args.length ? (
|
||||||
|
<div>
|
||||||
|
{func.args.map((item, index) => (
|
||||||
|
<Expression
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
fields={fields}
|
||||||
|
value={value?.args[index]}
|
||||||
|
valueField={{type: item.type} as any}
|
||||||
|
onChange={this.handleArgChange}
|
||||||
|
funcs={funcs}
|
||||||
|
defaultType={defaultType}
|
||||||
|
// allowedTypes={allowedTypes}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<span>)</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {value, classnames: cx, funcs} = this.props;
|
||||||
|
const func = value
|
||||||
|
? findTree(funcs!, item => (item as Func).type === value.func)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('CBFunc')}>
|
||||||
|
<PopOverContainer
|
||||||
|
popOverRender={({onClose}) => (
|
||||||
|
<ListRadios
|
||||||
|
onClick={onClose}
|
||||||
|
showRadio={false}
|
||||||
|
options={funcs!}
|
||||||
|
value={(func as Func)?.type}
|
||||||
|
option2value={option2value}
|
||||||
|
onChange={this.handleFuncChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({onClick, ref, isOpened}) => (
|
||||||
|
<div className={cx('CBFunc-select')}>
|
||||||
|
<ResultBox
|
||||||
|
className={cx(
|
||||||
|
'CBGroup-fieldInput',
|
||||||
|
isOpened ? 'is-active' : ''
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
allowInput={false}
|
||||||
|
result={func?.label}
|
||||||
|
onResultChange={noop}
|
||||||
|
onResultClick={onClick}
|
||||||
|
placeholder="请选择字段"
|
||||||
|
>
|
||||||
|
<span className={cx('CBGroup-fieldCaret')}>
|
||||||
|
<Icon icon="caret" className="icon" />
|
||||||
|
</span>
|
||||||
|
</ResultBox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</PopOverContainer>
|
||||||
|
|
||||||
|
{func ? (
|
||||||
|
this.renderFunc(func as Func)
|
||||||
|
) : (
|
||||||
|
<span className={cx('CBFunc-error')}>方法未定义</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default themeable(ConditionFunc);
|
|
@ -4,8 +4,10 @@ import {ClassNamesFn} from '../../theme';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import {ConditionItem} from './Item';
|
import {ConditionItem} from './Item';
|
||||||
import {autobind, guid} from '../../utils/helper';
|
import {autobind, guid} from '../../utils/helper';
|
||||||
|
import {Config} from './config';
|
||||||
|
|
||||||
export interface ConditionGroupProps {
|
export interface ConditionGroupProps {
|
||||||
|
config: Config;
|
||||||
value?: ConditionGroupValue;
|
value?: ConditionGroupValue;
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
|
@ -85,7 +87,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classnames: cx, value, fields, funcs} = this.props;
|
const {classnames: cx, value, fields, funcs, config} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('CBGroup')}>
|
<div className={cx('CBGroup')}>
|
||||||
|
@ -125,6 +127,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
? value!.children.map((item, index) =>
|
? value!.children.map((item, index) =>
|
||||||
(item as ConditionGroupValue).conjunction ? (
|
(item as ConditionGroupValue).conjunction ? (
|
||||||
<ConditionGroup
|
<ConditionGroup
|
||||||
|
config={config}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
value={item as ConditionGroupValue}
|
value={item as ConditionGroupValue}
|
||||||
|
@ -135,6 +138,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ConditionItem
|
<ConditionItem
|
||||||
|
config={config}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
value={item}
|
value={item}
|
||||||
|
|
|
@ -2,18 +2,17 @@ import React from 'react';
|
||||||
import PopOverContainer from '../PopOverContainer';
|
import PopOverContainer from '../PopOverContainer';
|
||||||
import {Icon} from '../icons';
|
import {Icon} from '../icons';
|
||||||
import ListRadios from '../ListRadios';
|
import ListRadios from '../ListRadios';
|
||||||
import {ClassNamesFn} from '../../theme';
|
import {ClassNamesFn, themeable, ThemeProps} from '../../theme';
|
||||||
|
|
||||||
export interface InputSwitchProps {
|
export interface InputSwitchProps extends ThemeProps {
|
||||||
options: Array<any>;
|
options: Array<any>;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
classnames: ClassNamesFn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const option2value = (item: any) => item.value;
|
const option2value = (item: any) => item.value;
|
||||||
|
|
||||||
export default function InputSwitch({
|
export function InputSwitch({
|
||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -42,3 +41,5 @@ export default function InputSwitch({
|
||||||
</PopOverContainer>
|
</PopOverContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default themeable(InputSwitch);
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Fields, ConditionRule, ConditionGroupValue, Funcs} from './types';
|
import {
|
||||||
|
Fields,
|
||||||
|
ConditionRule,
|
||||||
|
ConditionGroupValue,
|
||||||
|
Funcs,
|
||||||
|
ExpressionFunc,
|
||||||
|
Func,
|
||||||
|
Field,
|
||||||
|
FieldSimple,
|
||||||
|
ExpressionField
|
||||||
|
} from './types';
|
||||||
import {ClassNamesFn} from '../../theme';
|
import {ClassNamesFn} from '../../theme';
|
||||||
import {Icon} from '../icons';
|
import {Icon} from '../icons';
|
||||||
import Select from '../Select';
|
import {autobind, findTree, noop} from '../../utils/helper';
|
||||||
import {autobind} from '../../utils/helper';
|
import Expression from './Expression';
|
||||||
|
import {Config, OperationMap} from './config';
|
||||||
import PopOverContainer from '../PopOverContainer';
|
import PopOverContainer from '../PopOverContainer';
|
||||||
import InputBox from '../InputBox';
|
|
||||||
import ListRadios from '../ListRadios';
|
import ListRadios from '../ListRadios';
|
||||||
import ResultBox from '../ResultBox';
|
import ResultBox from '../ResultBox';
|
||||||
import ConditionField from './Field';
|
|
||||||
import InputSwitch from './InputSwitch';
|
const option2value = (item: any) => item.value;
|
||||||
|
|
||||||
export interface ConditionItemProps {
|
export interface ConditionItemProps {
|
||||||
|
config: Config;
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
@ -20,17 +31,6 @@ export interface ConditionItemProps {
|
||||||
onChange: (value: ConditionRule, index?: number) => void;
|
onChange: (value: ConditionRule, index?: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftInputOptions = [
|
|
||||||
{
|
|
||||||
label: '字段',
|
|
||||||
value: 'field'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '函数',
|
|
||||||
value: 'func'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export class ConditionItem extends React.Component<ConditionItemProps> {
|
export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
@autobind
|
@autobind
|
||||||
handleLeftFieldSelect(field: any) {
|
handleLeftFieldSelect(field: any) {
|
||||||
|
@ -54,34 +54,101 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
onChange(value, this.props.index);
|
onChange(value, this.props.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleLeftChange(leftValue: any) {
|
||||||
|
const value = {...this.props.value, left: leftValue};
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
|
||||||
|
onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleOperatorChange() {}
|
||||||
|
|
||||||
renderLeft() {
|
renderLeft() {
|
||||||
const {value, fields, classnames: cx, funcs} = this.props;
|
const {value, fields, funcs} = this.props;
|
||||||
const inputType =
|
|
||||||
value.left && (value.left as any).type === 'func' ? 'func' : 'field';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Expression
|
||||||
{Array.isArray(funcs) ? (
|
funcs={funcs}
|
||||||
<InputSwitch
|
value={value.left}
|
||||||
classnames={cx}
|
onChange={this.handleLeftChange}
|
||||||
onChange={this.handleLeftInputTypeChange}
|
fields={fields}
|
||||||
options={leftInputOptions}
|
defaultType="field"
|
||||||
value={inputType}
|
allowedTypes={['field', 'func']}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
|
|
||||||
{inputType === 'field' ? (
|
|
||||||
<ConditionField
|
|
||||||
classnames={cx}
|
|
||||||
options={fields}
|
|
||||||
value={value.left}
|
|
||||||
onChange={this.handleLeftFieldSelect}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderOperator() {
|
||||||
|
const {funcs, config, fields, value, classnames: cx} = this.props;
|
||||||
|
const left = value?.left;
|
||||||
|
let operators: Array<string> = [];
|
||||||
|
|
||||||
|
if ((left as ExpressionFunc)?.type === 'func') {
|
||||||
|
const func: Func = findTree(
|
||||||
|
funcs!,
|
||||||
|
(i: Func) => i.type === (left as ExpressionFunc).type
|
||||||
|
) as Func;
|
||||||
|
|
||||||
|
if (func) {
|
||||||
|
operators = config.types[func.returnType]?.operators;
|
||||||
|
}
|
||||||
|
} else if ((left as ExpressionField)?.type === 'field') {
|
||||||
|
const field: FieldSimple = findTree(
|
||||||
|
fields,
|
||||||
|
(i: FieldSimple) => i.name === (left as ExpressionField).field
|
||||||
|
) as FieldSimple;
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
operators = field.operators || config.types[field.type].operators;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(operators) && operators.length) {
|
||||||
|
return (
|
||||||
|
<PopOverContainer
|
||||||
|
popOverRender={({onClose}) => (
|
||||||
|
<ListRadios
|
||||||
|
onClick={onClose}
|
||||||
|
option2value={option2value}
|
||||||
|
onChange={this.handleOperatorChange}
|
||||||
|
options={operators.map(operator => ({
|
||||||
|
label: OperationMap[operator as keyof typeof OperationMap],
|
||||||
|
value: operator
|
||||||
|
}))}
|
||||||
|
value={value.op}
|
||||||
|
showRadio={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({onClick, isOpened, ref}) => (
|
||||||
|
<div className={cx('CBGroup-operator')}>
|
||||||
|
<ResultBox
|
||||||
|
className={cx(
|
||||||
|
'CBGroup-operatorInput',
|
||||||
|
isOpened ? 'is-active' : ''
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
allowInput={false}
|
||||||
|
result={OperationMap[value?.op as keyof typeof OperationMap]}
|
||||||
|
onResultChange={noop}
|
||||||
|
onResultClick={onClick}
|
||||||
|
placeholder="请选择操作"
|
||||||
|
>
|
||||||
|
<span className={cx('CBGroup-operatorCaret')}>
|
||||||
|
<Icon icon="caret" className="icon" />
|
||||||
|
</span>
|
||||||
|
</ResultBox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</PopOverContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderItem() {
|
renderItem() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -97,6 +164,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
|
|
||||||
<div className={cx('CBItem-itemBody')}>
|
<div className={cx('CBItem-itemBody')}>
|
||||||
{this.renderLeft()}
|
{this.renderLeft()}
|
||||||
|
{this.renderOperator()}
|
||||||
{this.renderItem()}
|
{this.renderItem()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class Value extends React.Component<any> {
|
||||||
|
render() {
|
||||||
|
return <p>Value</p>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {FieldTypes, OperatorType, Funcs, Fields} from './types';
|
import {FieldTypes, OperatorType, Funcs, Fields, Type} from './types';
|
||||||
|
|
||||||
export interface BaseFieldConfig {
|
export interface BaseFieldConfig {
|
||||||
operations: Array<OperatorType>;
|
operations: Array<OperatorType>;
|
||||||
|
@ -8,9 +8,38 @@ export interface Config {
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
maxLevel?: number;
|
maxLevel?: number;
|
||||||
|
types: {
|
||||||
|
[propName: string]: Type;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const OperationMap = {
|
||||||
|
equal: '等于',
|
||||||
|
not_equal: '不等于',
|
||||||
|
is_empty: '为空',
|
||||||
|
is_not_empty: '不为空',
|
||||||
|
like: 'LIKE',
|
||||||
|
not_like: 'NOT LIKE',
|
||||||
|
starts_with: 'Start With',
|
||||||
|
ends_with: 'Ends With'
|
||||||
|
};
|
||||||
|
|
||||||
const defaultConfig: Config = {
|
const defaultConfig: Config = {
|
||||||
|
types: {
|
||||||
|
text: {
|
||||||
|
operators: [
|
||||||
|
'equal',
|
||||||
|
'not_equal',
|
||||||
|
'is_empty',
|
||||||
|
'is_not_empty',
|
||||||
|
'like',
|
||||||
|
'not_like',
|
||||||
|
'starts_with',
|
||||||
|
'ends_with'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
fields: [
|
fields: [
|
||||||
// {
|
// {
|
||||||
// type: 'text',
|
// type: 'text',
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {LocaleProps, localeable} from '../../locale';
|
||||||
import {uncontrollable} from 'uncontrollable';
|
import {uncontrollable} from 'uncontrollable';
|
||||||
import {Fields, ConditionGroupValue, Funcs} from './types';
|
import {Fields, ConditionGroupValue, Funcs} from './types';
|
||||||
import {ConditionGroup} from './Group';
|
import {ConditionGroup} from './Group';
|
||||||
|
import defaultConfig from './config';
|
||||||
|
|
||||||
export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
|
@ -13,11 +14,14 @@ export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryBuilder extends React.Component<QueryBuilderProps> {
|
export class QueryBuilder extends React.Component<QueryBuilderProps> {
|
||||||
|
config = defaultConfig;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classnames: cx, fields, funcs, onChange, value} = this.props;
|
const {classnames: cx, fields, funcs, onChange, value} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionGroup
|
<ConditionGroup
|
||||||
|
config={this.config}
|
||||||
funcs={funcs}
|
funcs={funcs}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
value={value}
|
value={value}
|
||||||
|
|
|
@ -19,25 +19,31 @@ export type FieldItem = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExpressionSimple = string | number | object | undefined;
|
export type ExpressionSimple = string | number | object | undefined;
|
||||||
export type ExpressionComplex =
|
export type ExpressionValue =
|
||||||
| ExpressionSimple
|
| ExpressionSimple
|
||||||
| {
|
| {
|
||||||
type: 'value';
|
type: 'value';
|
||||||
value: ExpressionSimple;
|
value: ExpressionSimple;
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'func';
|
|
||||||
func: string;
|
|
||||||
args: Array<ExpressionComplex>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'field';
|
|
||||||
field: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'raw';
|
|
||||||
field: string;
|
|
||||||
};
|
};
|
||||||
|
export type ExpressionFunc = {
|
||||||
|
type: 'func';
|
||||||
|
func: string;
|
||||||
|
args: Array<ExpressionComplex>;
|
||||||
|
};
|
||||||
|
export type ExpressionField = {
|
||||||
|
type: 'field';
|
||||||
|
field: string;
|
||||||
|
};
|
||||||
|
export type ExpressionRaw = {
|
||||||
|
type: 'raw';
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExpressionComplex =
|
||||||
|
| ExpressionValue
|
||||||
|
| ExpressionFunc
|
||||||
|
| ExpressionField
|
||||||
|
| ExpressionRaw;
|
||||||
|
|
||||||
export interface ConditionRule {
|
export interface ConditionRule {
|
||||||
id: any;
|
id: any;
|
||||||
|
@ -59,6 +65,7 @@ interface BaseField {
|
||||||
type: FieldTypes;
|
type: FieldTypes;
|
||||||
label: string;
|
label: string;
|
||||||
valueTypes?: Array<'value' | 'field' | 'func' | 'expression'>;
|
valueTypes?: Array<'value' | 'field' | 'func' | 'expression'>;
|
||||||
|
operators?: Array<string>;
|
||||||
|
|
||||||
// valueTypes 里面配置 func 才有效。
|
// valueTypes 里面配置 func 才有效。
|
||||||
funcs?: Array<string>;
|
funcs?: Array<string>;
|
||||||
|
@ -123,7 +130,7 @@ interface GroupField {
|
||||||
children: Array<FieldSimple>;
|
children: Array<FieldSimple>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldSimple =
|
export type FieldSimple =
|
||||||
| TextField
|
| TextField
|
||||||
| NumberField
|
| NumberField
|
||||||
| DateField
|
| DateField
|
||||||
|
@ -150,3 +157,7 @@ export interface FuncArg extends BaseField {
|
||||||
}
|
}
|
||||||
export type Funcs = Array<Func | FuncGroup>;
|
export type Funcs = Array<Func | FuncGroup>;
|
||||||
export type Fields = Array<Field>;
|
export type Fields = Array<Field>;
|
||||||
|
|
||||||
|
export type Type = {
|
||||||
|
operators: Array<string>;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue