diff --git a/examples/components/Page/Form.jsx b/examples/components/Page/Form.jsx index c146ccf4..24528ad5 100644 --- a/examples/components/Page/Form.jsx +++ b/examples/components/Page/Form.jsx @@ -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', diff --git a/scss/_variables.scss b/scss/_variables.scss index ddc0aa30..fc33cce8 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -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; diff --git a/scss/components/_condition-builder.scss b/scss/components/_condition-builder.scss index c216c1ba..764fac43 100644 --- a/scss/components/_condition-builder.scss +++ b/scss/components/_condition-builder.scss @@ -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; } diff --git a/src/components/condition-builder/Expression.tsx b/src/components/condition-builder/Expression.tsx index 99c6f349..877cac1e 100644 --- a/src/components/condition-builder/Expression.tsx +++ b/src/components/condition-builder/Expression.tsx @@ -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 { return ( <> - {types.length > 1 ? ( - ({ - label: fieldMap[item], - value: item - }))} - /> - ) : null} - {inputType === 'value' ? ( { + (item as any).children || + (item as FieldSimple).type === valueField.type + ) + : fields! + } /> ) : null} @@ -169,6 +168,17 @@ export class Expression extends React.Component { allowedTypes={allowedTypes} /> ) : null} + + {types.length > 1 ? ( + ({ + label: fieldMap[item], + value: item + }))} + /> + ) : null} ); } diff --git a/src/components/condition-builder/Group.tsx b/src/components/condition-builder/Group.tsx index 4f21d632..10644a01 100644 --- a/src/components/condition-builder/Group.tsx +++ b/src/components/condition-builder/Group.tsx @@ -129,6 +129,7 @@ export class ConditionGroup extends React.Component { className="m-r-xs" size="xs" active={value?.not} + level={value?.not ? 'info' : 'default'} > 非 @@ -176,22 +177,24 @@ export class ConditionGroup extends React.Component {
- {Array.isArray(value?.children) - ? value!.children.map((item, index) => ( - 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) => ( + 1} + onDragStart={onDragStart} + config={config} + key={item.id} + fields={fields} + value={item as ConditionGroupValue} + index={index} + onChange={this.handleItemChange} + funcs={funcs} + onRemove={this.handleItemRemove} + /> + )) + ) : ( +
+ )}
); diff --git a/src/components/condition-builder/InputSwitch.tsx b/src/components/condition-builder/InputSwitch.tsx index 22a229de..53f14c88 100644 --- a/src/components/condition-builder/InputSwitch.tsx +++ b/src/components/condition-builder/InputSwitch.tsx @@ -34,7 +34,7 @@ export function InputSwitch({ {({onClick, isOpened, ref}) => ( )} diff --git a/src/components/condition-builder/Item.tsx b/src/components/condition-builder/Item.tsx index ad6e6565..893512b7 100644 --- a/src/components/condition-builder/Item.tsx +++ b/src/components/condition-builder/Item.tsx @@ -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 { @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 { @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 { 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 { 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 { 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 { } 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 { if (op === 'is_empty' || op === 'is_not_empty') { return null; + } else if (op === 'between' || op === 'not_between') { + return ( + <> + )?.[0]} + onChange={this.handleRightSubChange.bind(this, 0)} + fields={fields} + defaultType="value" + allowedTypes={ + field?.valueTypes || ['value', 'field', 'func', 'raw'] + } + /> + + ~ + + )?.[1]} + onChange={this.handleRightSubChange.bind(this, 1)} + fields={fields} + defaultType="value" + allowedTypes={ + field?.valueTypes || ['value', 'field', 'func', 'raw'] + } + /> + + ); } return ( diff --git a/src/components/condition-builder/Value.tsx b/src/components/condition-builder/Value.tsx index a3471570..0e7ab2de 100644 --- a/src/components/condition-builder/Value.tsx +++ b/src/components/condition-builder/Value.tsx @@ -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 { if (field.type === 'text') { input = ( ); + } else if (field.type === 'number') { + input = ( + + ); + } else if (field.type === 'date') { + input = ( + + ); + } else if (field.type === 'time') { + input = ( + + ); + } else if (field.type === 'datetime') { + input = ( + + ); } return
{input}
; diff --git a/src/components/condition-builder/config.ts b/src/components/condition-builder/config.ts index b212c31f..9e89649c 100644 --- a/src/components/condition-builder/config.ts +++ b/src/components/condition-builder/config.ts @@ -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' + ] } }, diff --git a/src/components/condition-builder/types.ts b/src/components/condition-builder/types.ts index cf1e9824..a149be03 100644 --- a/src/components/condition-builder/types.ts +++ b/src/components/condition-builder/types.ts @@ -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; defaultValue?: any; + placeholder?: string; } -type FieldGroup = { +export type FieldGroup = { label: string; children: Array; }; @@ -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; export type Fields = Array; export type Type = { - operators: Array; + defaultOp?: OperatorType; + operators: Array; placeholder?: string; }; diff --git a/src/components/icons.tsx b/src/components/icons.tsx index bc948981..41d2ceae 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -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 = ; @@ -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, diff --git a/src/icons/ellipsis-v.svg b/src/icons/ellipsis-v.svg new file mode 100644 index 00000000..bdc91484 --- /dev/null +++ b/src/icons/ellipsis-v.svg @@ -0,0 +1,9 @@ + + + + + + + + +