条件组合完善进度 80%

This commit is contained in:
2betop 2020-08-18 20:26:04 +08:00
parent b6afea8162
commit 0e78191ddc
12 changed files with 358 additions and 83 deletions

View File

@ -14,6 +14,18 @@ const fields = [
type: 'number'
},
{
label: '出生日期',
name: 'birthday',
type: 'date'
},
{
label: '起床时间',
name: 'wakeupAt',
type: 'time'
},
{
label: '入职时间',
name: 'ruzhi',
@ -39,22 +51,22 @@ const fields = [
];
const funcs = [
{
label: '文本',
children: [
{
type: 'LOWERCASE',
label: '转小写',
returnType: 'text',
args: [
{
type: 'text',
label: '文本'
}
]
}
]
}
// {
// label: '',
// children: [
// {
// type: 'LOWERCASE',
// label: '',
// returnType: 'text',
// args: [
// {
// type: 'text',
// label: ''
// }
// ]
// }
// ]
// }
];
export default {
@ -64,25 +76,39 @@ export default {
type: 'form',
mode: 'horizontal',
title: '',
data: {a: [{b: 1, c: [{d: 2}]}]},
// debug: true,
api: '/api/mock2/form/saveForm',
controls: [
{
label: 'Name',
type: 'text',
name: 'name'
},
// {
// label: 'Name',
// type: 'text',
// name: 'name'
// },
{
label: 'Email',
type: 'email',
name: 'email'
},
// {
// label: 'Email',
// type: 'email',
// name: 'email'
// },
{
name: 'a',
type: 'static',
tpl: '${a|json:2}'
},
// {
// name: 'a',
// type: 'static',
// tpl: '${a|json:2}'
// },
// {
// name: 'a.0.b',
// type: 'text',
// label: 'B'
// },
// {
// name: 'a.0.c.0.d',
// type: 'number',
// label: 'D'
// },
{
name: 'a',

View File

@ -1020,12 +1020,12 @@ $ColorPicker-paddingY: (
)/2 - $ColorPicker-borderWidth !default;
$ColorPicker-placeholderColor: $Form-input-placeholderColor !default;
$ColorPicker-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
$DatePicker-onHover-borderColor: $Form-input-borderColor !default;
// datepicker
$DatePicker-color: $Form-input-color !default;
$DatePicker-bg: $white !default;
$DatePicker-onHover-bg: darken($DatePicker-bg, 5%) !default;
$DatePicker-onHover-borderColor: $Form-input-onFocused-borderColor !default;
$DatePicker-onHover-bg: $DatePicker-bg !default;
$DatePicker-borderWidth: $Form-input-borderWidth !default;
$DatePicker-borderColor: $Form-input-borderColor !default;
$DatePicker-borderRadius: $Form-input-borderRadius !default;

View File

@ -41,14 +41,14 @@
&-operator {
position: relative;
display: inline-block;
min-width: px2rem(120px);
margin-left: $gap-xs;
margin: px2rem(3px);
vertical-align: middle;
}
&-fieldCaret,
&-operatorCaret {
transition: transform 0.3s ease-out;
margin: 0 $gap-xs;
margin: 3px;
display: flex;
color: $icon-color;
&:hover {
@ -66,6 +66,46 @@
&-operatorInput.is-active &-operatorCaret {
transform: rotate(180deg);
}
&-placeholder {
color: $text--muted-color;
position: relative;
margin-left: 30px;
padding: 10px;
background: rgba(0, 0, 0, 0.03);
border-radius: 5px;
&:before {
position: absolute;
content: '';
top: -10px;
left: -30px;
width: 20px;
border-left: solid 1px $borderColor;
bottom: 0;
}
&:after {
position: absolute;
content: '';
top: 50%;
width: 20px;
left: -30px;
border-top: solid 1px $borderColor;
}
&:last-child {
&:before {
border-bottom-left-radius: 5px;
border-bottom: solid 1px $borderColor;
bottom: 50%;
}
&:after {
display: none;
}
}
}
}
.#{$ns}CBDelete {
@ -85,7 +125,7 @@
&-body {
display: flex;
padding: 5px 10px;
padding: 2px 7px;
border-radius: 5px;
flex-direction: row;
align-items: center;
@ -125,7 +165,6 @@
}
.#{$ns}CBGroup {
margin-left: $gap-xs;
flex-grow: 1;
}
@ -172,7 +211,8 @@
.#{$ns}CBInputSwitch {
position: relative;
display: inline-block;
margin-left: 5px;
vertical-align: middle;
// margin: px2rem(3px);
cursor: pointer;
> a {
@include icon-color();
@ -186,7 +226,8 @@
.#{$ns}CBFunc {
display: inline-block;
margin-left: $gap-xs;
vertical-align: middle;
margin: px2rem(3px);
&-select {
display: inline-block;
@ -214,5 +255,13 @@
.#{$ns}CBValue {
position: relative;
display: inline-block;
margin-left: $gap-xs;
vertical-align: middle;
margin: px2rem(3px);
}
.#{$ns}CBSeprator {
width: 20px;
text-align: center;
display: inline-block;
user-select: none;
}

View File

@ -5,11 +5,12 @@ import {
Func,
ExpressionFunc,
Type,
FieldSimple
FieldSimple,
FieldGroup
} from './types';
import React from 'react';
import ConditionField from './Field';
import {autobind, findTree} from '../../utils/helper';
import {autobind, findTree, filterTree} from '../../utils/helper';
import Value from './Value';
import InputSwitch from './InputSwitch';
import ConditionFunc from './Func';
@ -132,17 +133,6 @@ export class Expression extends React.Component<ExpressionProps> {
return (
<>
{types.length > 1 ? (
<InputSwitch
value={inputType}
onChange={this.handleInputTypeChange}
options={types.map(item => ({
label: fieldMap[item],
value: item
}))}
/>
) : null}
{inputType === 'value' ? (
<Value
field={valueField!}
@ -155,7 +145,16 @@ export class Expression extends React.Component<ExpressionProps> {
<ConditionField
value={(value as any)?.field}
onChange={this.handleFieldChange}
options={fields!}
options={
valueField
? filterTree(
fields!,
item =>
(item as any).children ||
(item as FieldSimple).type === valueField.type
)
: fields!
}
/>
) : null}
@ -169,6 +168,17 @@ export class Expression extends React.Component<ExpressionProps> {
allowedTypes={allowedTypes}
/>
) : null}
{types.length > 1 ? (
<InputSwitch
value={inputType}
onChange={this.handleInputTypeChange}
options={types.map(item => ({
label: fieldMap[item],
value: item
}))}
/>
) : null}
</>
);
}

View File

@ -129,6 +129,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
className="m-r-xs"
size="xs"
active={value?.not}
level={value?.not ? 'info' : 'default'}
>
</Button>
@ -176,22 +177,24 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
</div>
<div className={cx('CBGroup-body')}>
{Array.isArray(value?.children)
? value!.children.map((item, index) => (
<GroupOrItem
draggable={value!.children!.length > 1}
onDragStart={onDragStart}
config={config}
key={item.id}
fields={fields}
value={item as ConditionGroupValue}
index={index}
onChange={this.handleItemChange}
funcs={funcs}
onRemove={this.handleItemRemove}
/>
))
: null}
{Array.isArray(value?.children) && value!.children.length ? (
value!.children.map((item, index) => (
<GroupOrItem
draggable={value!.children!.length > 1}
onDragStart={onDragStart}
config={config}
key={item.id}
fields={fields}
value={item as ConditionGroupValue}
index={index}
onChange={this.handleItemChange}
funcs={funcs}
onRemove={this.handleItemRemove}
/>
))
) : (
<div className={cx('CBGroup-placeholder')}></div>
)}
</div>
</div>
);

View File

@ -34,7 +34,7 @@ export function InputSwitch({
{({onClick, isOpened, ref}) => (
<div className={cx('CBInputSwitch', isOpened ? 'is-active' : '')}>
<a onClick={onClick} ref={ref}>
<Icon icon="setting" />
<Icon icon="ellipsis-v" />
</a>
</div>
)}

View File

@ -9,7 +9,8 @@ import {
Field,
FieldSimple,
ExpressionField,
OperatorType
OperatorType,
ExpressionComplex
} from './types';
import {ThemeProps, themeable} from '../../theme';
import {Icon} from '../icons';
@ -56,7 +57,12 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
@autobind
handleLeftChange(leftValue: any) {
const value = {...this.props.value, left: leftValue};
const value = {
...this.props.value,
left: leftValue,
op: undefined,
right: undefined
};
const onChange = this.props.onChange;
onChange(value, this.props.index);
@ -64,7 +70,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
@autobind
handleOperatorChange(op: OperatorType) {
const value = {...this.props.value, op: op};
const value = {...this.props.value, op: op, right: undefined};
this.props.onChange(value, this.props.index);
}
@ -76,6 +82,18 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
onChange(value, this.props.index);
}
handleRightSubChange(index: number, rightValue: any) {
const origin = Array.isArray(this.props.value?.right)
? this.props.value.right.concat()
: [];
origin[index] = rightValue;
const value = {...this.props.value, right: origin};
const onChange = this.props.onChange;
onChange(value, this.props.index);
}
renderLeft() {
const {value, fields, funcs} = this.props;
@ -99,7 +117,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
if ((left as ExpressionFunc)?.type === 'func') {
const func: Func = findTree(
funcs!,
(i: Func) => i.type === (left as ExpressionFunc).type
(i: Func) => i.type === (left as ExpressionFunc).func
) as Func;
if (func) {
@ -173,7 +191,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
if ((left as ExpressionFunc)?.type === 'func') {
const func: Func = findTree(
funcs!,
(i: Func) => i.type === (left as ExpressionFunc).type
(i: Func) => i.type === (left as ExpressionFunc).func
) as Func;
if (func) {
@ -198,7 +216,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
}
renderRightWidgets(type: string, op: OperatorType) {
const {funcs, value, fields, config} = this.props;
const {funcs, value, fields, config, classnames: cx} = this.props;
let field = {
...config.types[type],
type
@ -220,6 +238,36 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
if (op === 'is_empty' || op === 'is_not_empty') {
return null;
} else if (op === 'between' || op === 'not_between') {
return (
<>
<Expression
funcs={funcs}
valueField={field}
value={(value.right as Array<ExpressionComplex>)?.[0]}
onChange={this.handleRightSubChange.bind(this, 0)}
fields={fields}
defaultType="value"
allowedTypes={
field?.valueTypes || ['value', 'field', 'func', 'raw']
}
/>
<span className={cx('CBSeprator')}>~</span>
<Expression
funcs={funcs}
valueField={field}
value={(value.right as Array<ExpressionComplex>)?.[1]}
onChange={this.handleRightSubChange.bind(this, 1)}
fields={fields}
defaultType="value"
allowedTypes={
field?.valueTypes || ['value', 'field', 'func', 'raw']
}
/>
</>
);
}
return (

View File

@ -2,6 +2,8 @@ import React from 'react';
import {FieldSimple} from './types';
import {ThemeProps, themeable} from '../../theme';
import InputBox from '../InputBox';
import NumberInput from '../NumberInput';
import DatePicker from '../DatePicker';
export interface ValueProps extends ThemeProps {
value: any;
@ -17,11 +19,56 @@ export class Value extends React.Component<ValueProps> {
if (field.type === 'text') {
input = (
<InputBox
value={value}
value={value ?? field.defaultValue}
onChange={onChange}
placeholder={field.placeholder}
/>
);
} else if (field.type === 'number') {
input = (
<NumberInput
placeholder={field.placeholder || '请选择日期'}
min={field.minimum}
max={field.maximum}
value={value ?? field.defaultValue}
onChange={onChange}
/>
);
} else if (field.type === 'date') {
input = (
<DatePicker
placeholder={field.placeholder || '请选择日期'}
format={field.format || 'YYYY-MM-DD'}
inputFormat={field.inputFormat || 'YYYY-MM-DD'}
value={value ?? field.defaultValue}
onChange={onChange}
timeFormat=""
/>
);
} else if (field.type === 'time') {
input = (
<DatePicker
viewMode="time"
placeholder={field.placeholder || '请选择时间'}
format={field.format || 'HH:mm'}
inputFormat={field.inputFormat || 'HH:mm'}
value={value ?? field.defaultValue}
onChange={onChange}
dateFormat=""
timeFormat={field.format || 'HH:mm'}
/>
);
} else if (field.type === 'datetime') {
input = (
<DatePicker
placeholder={field.placeholder || '请选择日期时间'}
format={field.format || ''}
inputFormat={field.inputFormat || 'YYYY-MM-DD HH:mm'}
value={value ?? field.defaultValue}
onChange={onChange}
timeFormat={field.timeFormat || 'HH:mm'}
/>
);
}
return <div className={cx('CBValue')}>{input}</div>;

View File

@ -16,6 +16,12 @@ export interface Config {
export const OperationMap = {
equal: '等于',
not_equal: '不等于',
less: '小于',
less_or_equal: '小于或等于',
greater: '大于',
greater_or_equal: '大于或等于',
between: '属于范围',
not_between: '不属于范围',
is_empty: '为空',
is_not_empty: '不为空',
like: '模糊匹配',
@ -28,6 +34,7 @@ const defaultConfig: Config = {
types: {
text: {
placeholder: '请输入文本',
defaultOp: 'equal',
operators: [
'equal',
'not_equal',
@ -38,6 +45,64 @@ const defaultConfig: Config = {
'starts_with',
'ends_with'
]
},
number: {
operators: [
'equal',
'not_equal',
'less',
'less_or_equal',
'greater',
'greater_or_equal',
'between',
'not_between',
'is_empty',
'is_not_empty'
]
},
date: {
operators: [
'equal',
'not_equal',
'less',
'less_or_equal',
'greater',
'greater_or_equal',
'between',
'not_between',
'is_empty',
'is_not_empty'
]
},
time: {
operators: [
'equal',
'not_equal',
'less',
'less_or_equal',
'greater',
'greater_or_equal',
'between',
'not_between',
'is_empty',
'is_not_empty'
]
},
datetime: {
operators: [
'equal',
'not_equal',
'less',
'less_or_equal',
'greater',
'greater_or_equal',
'between',
'not_between',
'is_empty',
'is_not_empty'
]
}
},

View File

@ -15,7 +15,13 @@ export type OperatorType =
| 'like'
| 'not_like'
| 'starts_with'
| 'ends_with';
| 'ends_with'
| 'less'
| 'less_or_equal'
| 'greater'
| 'greater_or_equal'
| 'between'
| 'not_between';
export type FieldItem = {
type: 'text';
@ -75,9 +81,10 @@ interface BaseField {
funcs?: Array<string>;
defaultValue?: any;
placeholder?: string;
}
type FieldGroup = {
export type FieldGroup = {
label: string;
children: Array<FieldSimple>;
};
@ -85,7 +92,6 @@ type FieldGroup = {
interface TextField extends BaseField {
name: string;
type: 'text';
placeholder?: string;
minLength?: number;
maxLength?: number;
}
@ -100,6 +106,8 @@ interface NumberField extends BaseField {
interface DateField extends BaseField {
name: string;
type: 'date';
format?: string;
inputFormat?: string;
minDate?: any;
maxDate?: any;
}
@ -109,11 +117,16 @@ interface TimeField extends BaseField {
type: 'time';
minTime?: any;
maxTime?: any;
format?: string;
inputFormat?: string;
}
interface DatetimeField extends BaseField {
type: 'datetime';
name: string;
format?: string;
inputFormat?: string;
timeFormat?: string;
}
interface SelectField extends BaseField {
@ -164,6 +177,7 @@ export type Funcs = Array<Func | FuncGroup>;
export type Fields = Array<Field>;
export type Type = {
operators: Array<string>;
defaultOp?: OperatorType;
operators: Array<OperatorType>;
placeholder?: string;
};

View File

@ -134,6 +134,9 @@ import SettingIcon from '../icons/setting.svg';
// @ts-ignore
import PlusCicleIcon from '../icons/plus-cicle.svg';
// @ts-ignore
import EllipsisVIcon from '../icons/ellipsis-v.svg';
// 兼容原来的用法,后续不直接试用。
// @ts-ignore
export const closeIcon = <CloseIcon />;
@ -221,6 +224,7 @@ registerIcon('sort-asc', SortAscIcon);
registerIcon('sort-desc', SortDescIcon);
registerIcon('setting', SettingIcon);
registerIcon('plus-cicle', PlusCicleIcon);
registerIcon('ellipsis-v', EllipsisVIcon);
export function Icon({
icon,

9
src/icons/ellipsis-v.svg Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 26 126" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ellipsis-vertical" transform="translate(0.500000, 0.500000)" fill="currentColor" fill-rule="nonzero">
<path d="M12.5,0 C5.625,0 0,5.625 0,12.5 C0,19.375 5.625,25 12.5,25 C19.375,25 25,19.375 25,12.5 C25,5.625 19.375,0 12.5,0 Z M12.5,50 C5.625,50 0,55.625 0,62.5 C0,69.375 5.625,75 12.5,75 C19.375,75 25,69.375 25,62.5 C25,55.625 19.375,50 12.5,50 Z M12.5,100 C5.625,100 0,105.625 0,112.5 C0,119.375 5.625,125 12.5,125 C19.375,125 25,119.375 25,112.5 C25,105.625 19.375,100 12.5,100 Z" id="形状">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 767 B