组合条件控件补充功能&添加文档 (#1580)

* Condition-Builder 优化 + 补充文档

* 完善文档
This commit is contained in:
liaoxuezhi 2021-02-19 09:56:19 +08:00 committed by GitHub
parent 0e420dfa47
commit ae11ca586d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 885 additions and 150 deletions

View File

@ -0,0 +1,357 @@
---
title: 组合条件
description:
type: 0
group: null
menuName: 组合条件
icon:
---
## 基本用法
用于设置复杂组合条件,支持添加条件,添加分组,设置组合方式,拖拽排序等功能。
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "文本",
"type": "text",
"name": "text"
},
{
"label": "数字",
"type": "number",
"name": "number"
},
{
"label": "布尔",
"type": "boolean",
"name": "boolean"
},
{
"label": "选项",
"type": "select",
"name": "select",
"options": [
{
"label": "A",
"value": "a"
},
{
"label": "B",
"value": "b"
},
{
"label": "C",
"value": "c"
},
{
"label": "D",
"value": "d"
},
{
"label": "E",
"value": "e"
}
]
},
{
"label": "动态选项",
"type": "select",
"name": "select2",
"source": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/getOptions?waitSeconds=1"
},
{
"label": "日期",
"children": [
{
"label": "日期",
"type": "date",
"name": "date"
},
{
"label": "时间",
"type": "time",
"name": "time"
},
{
"label": "日期时间",
"type": "datetime",
"name": "datetime"
}
]
}
]
}
]
}
```
## 值格式说明
```ts
type ValueGroup = {
conjunction: 'and' | 'or';
children: Array<ValueGroup | ValueItem>;
};
type ValueItem = {
// 左侧字段,这块有预留类型,不过目前基本上只是字段。
left: {
type: 'field';
field: string;
};
// 还有更多类型,暂不细说
op: 'equals' | 'not_equal' | 'less' | 'less_or_equal';
// 根据字段类型和 op 的不同,值格式会不一样。
// 如果 op 是范围right 就是个数组 [开始值,结束值],否则就是值。
right: any;
};
type Value = ValueGroup;
```
## 字段选项
字段选项为这个组件主要配置部分,通过 `fields` 字段来配置,有哪些字段,并且字段的类型是什么,支持哪些比较操作符。
`fields` 为数组类型,每个成员表示一个可选字段,支持多个层,配置示例
```json
"fields": [
{
"label": "字段1"
// 字段1
},
{
"label": "字段2"
// 字段2
},
{
"label": "字段分组",
"children": [
{
"label": "字段3"
},
{
"label": "字段4"
}
]
}
]
```
## 支持的字段类型
这里面能用的字段类型和表单项中的字段类型不一样,还没支持那么多,基本上只有一些基础的类型,其他复杂类型还需后续扩充,目前基本上支持以下这些类型。
### 文本
- `type` 字段配置中配置成 `"text"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'equal', 'not_equal', 'is_empty', 'is_not_empty', 'like', 'not_like', 'starts_with', 'ends_with' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp` 默认为 `"equal"`
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "text",
"name": "a"
}
]
}
]
}
```
### 数字
- `type` 字段配置中配置成 `"number"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between', 'is_empty', 'is_not_empty' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp` 默认为 `"equal"`
- `minimum` 最小值
- `maximum` 最大值
- `step` 步长
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "number",
"name": "a",
"minimum": 1,
"maximum": 10,
"step": 1
}
]
}
]
}
```
### 日期
- `type` 字段配置中配置成 `"date"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between', 'is_empty', 'is_not_empty' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp` 默认为 `"equal"`
- `defaultValue` 默认值
- `format` 默认 `"YYYY-MM-DD"` 值格式
- `inputFormat` 默认 `"YYYY-MM-DD"` 显示的日期格式。
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "date",
"name": "a"
}
]
}
]
}
```
### 日期时间
- `type` 字段配置中配置成 `"datetime"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between', 'is_empty', 'is_not_empty' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp` 默认为 `"equal"`
- `defaultValue` 默认值
- `format` 默认 `""` 值格式
- `inputFormat` 默认 `"YYYY-MM-DD HH:mm"` 显示的日期格式。
- `timeFormat` 默认 `"HH:mm"` 时间格式,决定输入框有哪些。
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "datetime",
"name": "a"
}
]
}
]
}
```
### 时间
- `type` 字段配置中配置成 `"time"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between', 'is_empty', 'is_not_empty' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp` 默认为 `"equal"`
- `defaultValue` 默认值
- `format` 默认 `"HH:mm"` 值格式
- `inputFormat` 默认 `"HH:mm"` 显示的日期格式。
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "time",
"name": "a"
}
]
}
]
}
```
### 下拉选择
- `type` 字段配置中配置成 `"select"`
- `label` 字段名称。
- `placeholder` 占位符
- `operators` 默认为 `[ 'select_equals', 'select_not_equals', 'select_any_in', 'select_not_any_in' ]` 如果不要那么多,可以配置覆盖。
- `defaultOp`
- `options` 选项列表,`Array<{label: string, value: any}>`
- `source` 动态选项,请配置 api。
- `searchable` 是否可以搜索
```schema: scope="body"
{
"type": "form",
"debug": true,
"controls": [
{
"type": "condition-builder",
"label": "条件组件",
"name": "conditions",
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
"fields": [
{
"label": "A",
"type": "select",
"name": "a",
"source": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/getOptions?waitSeconds=1",
"searchable": true
}
]
}
]
}
```

View File

@ -306,6 +306,15 @@ export const components = [
makeMarkdownRenderer
)
},
{
label: 'Condition-Builder 条件组合',
path: '/zh-CN/components/form/condition-builder',
getComponent: () =>
// @ts-ignore
import('../../docs/zh-CN/components/form/condition-builder.md').then(
makeMarkdownRenderer
)
},
{
label: 'Date 日期选择器',
path: '/zh-CN/components/form/date',

View File

@ -169,7 +169,7 @@
cursor: move;
width: 20px;
margin-left: -5px;
opacity: 0;
opacity: 0.6;
text-align: center;
transition: opacity var(--animation-duration) ease-out;
@include icon-color();

View File

@ -3,8 +3,9 @@ import qs from 'qs';
import React from 'react';
import Alert from './components/Alert2';
import ImageGallery from './components/ImageGallery';
import {RendererEnv} from './env';
import {envOverwrite} from './envOverwrite';
import {RendererEnv, RendererProps} from './factory';
import {RendererProps} from './factory';
import {LocaleContext, TranslateFn} from './locale';
import {RootRenderer} from './RootRenderer';
import {SchemaRenderer} from './SchemaRenderer';

View File

@ -454,4 +454,73 @@ export interface BaseSchema {
visibleOn?: SchemaExpression;
}
export interface Option {
/**
*
*/
label?: string;
/**
* Option
*
*
*/
scopeLabel?: string;
/**
*
*/
value?: any;
/**
*
*/
disabled?: boolean;
/**
*
*/
children?: Options;
/**
*
*/
visible?: boolean;
/**
* visible
*
* @deprecated visible
*/
hidden?: boolean;
/**
*
*/
description?: string;
/**
*
*/
defer?: boolean;
/**
* source
*/
deferApi?: SchemaApi;
/**
* defer true
*/
loading?: boolean;
/**
* defer
*/
loaded?: boolean;
[propName: string]: any;
}
export interface Options extends Array<Option> {}
export {PageSchema};

View File

@ -26,76 +26,10 @@ import Input from './Input';
import {Api} from '../types';
import {LocaleProps, localeable} from '../locale';
import Spinner from './Spinner';
import {SchemaApi} from '../Schema';
import {Option, Options} from '../Schema';
import {withRemoteOptions} from './WithRemoteOptions';
export interface Option {
/**
*
*/
label?: string;
/**
* Option
*
*
*/
scopeLabel?: string;
/**
*
*/
value?: any;
/**
*
*/
disabled?: boolean;
/**
*
*/
children?: Options;
/**
*
*/
visible?: boolean;
/**
* visible
*
* @deprecated visible
*/
hidden?: boolean;
/**
*
*/
description?: string;
/**
*
*/
defer?: boolean;
/**
* source
*/
deferApi?: SchemaApi;
/**
* defer true
*/
loading?: boolean;
/**
* defer
*/
loaded?: boolean;
[propName: string]: any;
}
export interface Options extends Array<Option> {}
export {Option, Options};
export interface OptionProps {
className?: string;
@ -105,6 +39,7 @@ export interface OptionProps {
labelField?: string;
simpleValue?: boolean; // 默认onChange 出去是整个 option 节点,如果配置了 simpleValue 就只包含值。
options: Options;
loading: boolean;
joinValues?: boolean;
extractValue?: boolean;
delimiter?: string;
@ -330,7 +265,6 @@ interface SelectProps extends OptionProps, ThemeProps, LocaleProps {
value: any;
loadOptions?: Function;
searchPromptText: string;
loading?: boolean;
loadingPlaceholder: string;
spinnerClassName?: string;
noResultsText: string;
@ -1067,10 +1001,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
}
}
export default themeable(
const enhancedSelect = themeable(
localeable(
uncontrollable(Select, {
value: 'onChange'
})
)
);
export default enhancedSelect;
export const SelectWithRemoteOptions = withRemoteOptions(enhancedSelect);

View File

@ -0,0 +1,195 @@
/**
*
*
* renderer/form/options
*
* hoc
*/
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
import {Api, Payload} from '../types';
import {Option, SchemaApi, SchemaTokenizeableString} from '../Schema';
import {withStore} from './WithStore';
import {EnvContext, RendererEnv} from '../env';
import {flow, Instance, types} from 'mobx-state-tree';
import {buildApi, isEffectiveApi} from '../utils/api';
import {isPureVariable, resolveVariableAndFilter} from '../utils/tpl-builtin';
import {normalizeOptions} from './Select';
import {reaction} from 'mobx';
export const Store = types
.model('OptionsStore')
.props({
fetching: false,
errorMsg: '',
options: types.frozen<Array<Option>>([]),
data: types.frozen({})
})
.actions(self => {
const load: (env: RendererEnv, api: Api, data: any) => Promise<any> = flow(
function* (env, api, data) {
try {
self.fetching = true;
const ret: Payload = yield env.fetcher(api, data);
if (ret.ok) {
const data = ret.data || {};
let options = data.options || data.items || data.rows || data;
(self as any).setOptions(options);
} else {
throw new Error(ret.msg || 'fetch error');
}
} catch (e) {
self.errorMsg = e.message;
} finally {
self.fetching = false;
}
}
);
return {
load,
setData(data: any) {
self.data = data || {};
},
setOptions(options: any) {
options = normalizeOptions(options);
if (Array.isArray(options)) {
self.options = options.concat();
}
}
};
});
export type IStore = Instance<typeof Store>;
export interface RemoteOptionsProps {
options: Array<Option>;
loading: boolean;
}
export interface OutterProps {
env?: RendererEnv;
data: any;
source?: SchemaApi | SchemaTokenizeableString;
options?: Array<Option>;
}
export function withRemoteOptions<
T extends React.ComponentType<React.ComponentProps<T> & RemoteOptionsProps>
>(ComposedComponent: T) {
type FinalOutterProps = JSX.LibraryManagedAttributes<
T,
Omit<React.ComponentProps<T>, keyof RemoteOptionsProps>
> &
OutterProps;
const result = hoistNonReactStatic(
withStore(() => Store.create())(
class extends React.Component<
FinalOutterProps & {
store: IStore;
}
> {
static displayName = `WithRemoteOptions(${
ComposedComponent.displayName || ComposedComponent.name
})`;
static ComposedComponent = ComposedComponent;
static contextType = EnvContext;
toDispose: Array<() => void> = [];
componentDidMount() {
const env: RendererEnv = this.props.env || this.context;
const {store, source, data, options} = this.props;
store.setData(data);
options && store.setOptions(options);
if (isPureVariable(source)) {
this.syncOptions();
this.toDispose.push(
reaction(
() =>
resolveVariableAndFilter(
source as string,
store.data,
'| raw'
),
() => this.syncOptions()
)
);
} else if (env && isEffectiveApi(source, data)) {
this.loadOptions();
this.toDispose.push(
reaction(
() =>
buildApi(source as string, store.data, {
ignoreData: true
}).url,
() => this.loadOptions()
)
);
}
}
componentDidUpdate(prevProps: any) {
const props = this.props;
if (props.data !== prevProps.data) {
props.store.setData(props.data);
}
}
componentWillUnmount() {
this.toDispose.forEach(fn => fn());
this.toDispose = [];
}
loadOptions() {
const env: RendererEnv = this.props.env || this.context;
const {store, source, data, options} = this.props;
if (env && isEffectiveApi(source, data)) {
store.load(env, source, data);
}
}
syncOptions() {
const {store, source, data} = this.props;
if (isPureVariable(source)) {
store.setOptions(
resolveVariableAndFilter(source as string, data, '| raw') || []
);
}
}
render() {
const store = this.props.store;
const injectedProps: RemoteOptionsProps = {
options: store.options,
loading: store.fetching
};
return (
<ComposedComponent
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
{...injectedProps}
/>
);
}
}
),
ComposedComponent
);
return result as typeof result & {
ComposedComponent: T;
};
}

View File

@ -0,0 +1,73 @@
/**
* store 使
* withStore store
* store
*/
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
import {destroy, IAnyStateTreeNode} from 'mobx-state-tree';
import {observer} from 'mobx-react';
export function withStore<K extends IAnyStateTreeNode>(
storeFactory: (props: any) => K
) {
return function <
T extends React.ComponentType<
React.ComponentProps<T> & {
store: K;
}
>
>(ComposedComponent: T) {
ComposedComponent = observer(ComposedComponent);
type OuterProps = JSX.LibraryManagedAttributes<
T,
Omit<React.ComponentProps<T>, 'store'>
>;
const result = hoistNonReactStatic(
class extends React.Component<OuterProps> {
static displayName = `WithStore(${
ComposedComponent.displayName || 'Unkown'
})`;
static ComposedComponent = ComposedComponent;
ref: any;
store?: K = storeFactory(this.props);
refFn = (ref: any) => {
this.ref = ref;
};
componentWillUnmount() {
this.store && destroy(this.store);
delete this.store;
}
getWrappedInstance() {
return this.ref;
}
render() {
const injectedProps = {
store: this.store
};
return (
<ComposedComponent
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
{...injectedProps}
ref={this.refFn}
/>
);
}
},
ComposedComponent
);
return result as typeof result & {
ComposedComponent: T;
};
};
}

View File

@ -31,6 +31,7 @@ import Formula from './Formula';
export interface ExpressionProps extends ThemeProps {
value: ExpressionComplex;
data?: any;
index?: number;
onChange: (value: ExpressionComplex, index?: number) => void;
valueField?: FieldSimple;
@ -124,7 +125,8 @@ export class Expression extends React.Component<ExpressionProps> {
fields,
op,
classnames: cx,
config
config,
data
} = this.props;
const inputType =
((value as any)?.type === 'field'
@ -153,6 +155,7 @@ export class Expression extends React.Component<ExpressionProps> {
value={value}
onChange={this.handleValueChange}
op={op}
data={data}
/>
) : null}

View File

@ -1,13 +1,11 @@
import React from 'react';
import {Fields, ConditionGroupValue, Funcs} from './types';
import {ClassNamesFn, ThemeProps, themeable} from '../../theme';
import {ThemeProps, themeable} from '../../theme';
import Button from '../Button';
import GroupOrItem from './GroupOrItem';
import {autobind, guid} from '../../utils/helper';
import {Config} from './config';
import {Icon} from '../icons';
import PopOverContainer from '../PopOverContainer';
import ListRadios from '../ListRadios';
export interface ConditionGroupProps extends ThemeProps {
config: Config;
@ -15,6 +13,7 @@ export interface ConditionGroupProps extends ThemeProps {
fields: Fields;
funcs?: Funcs;
showNot?: boolean;
data?: any;
onChange: (value: ConditionGroupValue) => void;
removeable?: boolean;
onRemove?: (e: React.MouseEvent) => void;
@ -113,6 +112,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
const {
classnames: cx,
value,
data,
fields,
funcs,
config,
@ -167,13 +167,12 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
</Button>
</div>
{removeable ? (
<a className={cx('CBDelete')} onClick={onRemove}>
<Icon icon="close" className="icon" />
</a>
) : null}
</div>
{removeable ? (
<a className={cx('CBDelete')} onClick={onRemove}>
<Icon icon="close" className="icon" />
</a>
) : null}
</div>
<div className={cx('CBGroup-body')}>
@ -190,6 +189,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
onChange={this.handleItemChange}
funcs={funcs}
onRemove={this.handleItemRemove}
data={data}
/>
))
) : (

View File

@ -13,6 +13,7 @@ export interface CBGroupOrItemProps extends ThemeProps {
fields: Fields;
funcs?: Funcs;
index: number;
data?: any;
draggable?: boolean;
onChange: (value: ConditionGroupValue, index: number) => void;
removeable?: boolean;
@ -39,6 +40,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
fields,
funcs,
draggable,
data,
onDragStart
} = this.props;
@ -65,6 +67,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
funcs={funcs}
removeable
onRemove={this.handleItemRemove}
data={data}
/>
) : (
<>
@ -74,6 +77,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
value={value as ConditionValue}
onChange={this.handleItemChange}
funcs={funcs}
data={data}
/>
<a className={cx('CBDelete')} onClick={this.handleItemRemove}>
<Icon icon="close" className="icon" />

View File

@ -29,6 +29,7 @@ export interface ConditionItemProps extends ThemeProps {
funcs?: Funcs;
index?: number;
value: ConditionRule;
data?: any;
onChange: (value: ConditionRule, index?: number) => void;
}
@ -220,7 +221,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
}
renderRightWidgets(type: string, op: OperatorType) {
const {funcs, value, fields, config, classnames: cx} = this.props;
const {funcs, value, data, fields, config, classnames: cx} = this.props;
let field = {
...config.types[type],
type
@ -250,6 +251,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
funcs={funcs}
valueField={field}
value={(value.right as Array<ExpressionComplex>)?.[0]}
data={data}
onChange={this.handleRightSubChange.bind(this, 0)}
fields={fields}
allowedTypes={
@ -265,6 +267,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
funcs={funcs}
valueField={field}
value={(value.right as Array<ExpressionComplex>)?.[1]}
data={data}
onChange={this.handleRightSubChange.bind(this, 1)}
fields={fields}
allowedTypes={
@ -283,6 +286,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
funcs={funcs}
valueField={field}
value={value.right}
data={data}
onChange={this.handleRightChange}
fields={fields}
allowedTypes={

View File

@ -4,12 +4,13 @@ import {ThemeProps, themeable} from '../../theme';
import InputBox from '../InputBox';
import NumberInput from '../NumberInput';
import DatePicker from '../DatePicker';
import Select from '../Select';
import {SelectWithRemoteOptions as Select} from '../Select';
import Switch from '../Switch';
import {localeable, LocaleProps} from '../../locale';
export interface ValueProps extends ThemeProps, LocaleProps {
value: any;
data?: any;
onChange: (value: any) => void;
field: FieldSimple;
op?: OperatorType;
@ -23,7 +24,8 @@ export class Value extends React.Component<ValueProps> {
value,
onChange,
op,
translate: __
translate: __,
data
} = this.props;
let input: JSX.Element | undefined = undefined;
@ -39,8 +41,10 @@ export class Value extends React.Component<ValueProps> {
input = (
<NumberInput
placeholder={field.placeholder || __('NumberInput.placeholder')}
step={field.step}
min={field.minimum}
max={field.maximum}
precision={field.precision}
value={value ?? field.defaultValue}
onChange={onChange}
/>
@ -84,8 +88,11 @@ export class Value extends React.Component<ValueProps> {
input = (
<Select
simpleValue
options={field.options!}
value={value ?? field.defaultValue}
options={field.options}
source={field.source}
searchable={field.searchable}
value={value ?? field.defaultValue ?? ''}
data={data}
onChange={onChange}
multiple={op === 'select_any_in' || op === 'select_not_any_in'}
/>

View File

@ -21,6 +21,7 @@ export interface ConditionBuilderProps extends ThemeProps, LocaleProps {
funcs?: Funcs;
showNot?: boolean;
value?: ConditionGroupValue;
data?: any;
onChange: (value: ConditionGroupValue) => void;
config?: Config;
}
@ -195,7 +196,8 @@ export class QueryBuilder extends React.Component<ConditionBuilderProps> {
funcs,
onChange,
value,
showNot
showNot,
data
} = this.props;
const normalizedValue = Array.isArray(value?.children)
@ -225,6 +227,7 @@ export class QueryBuilder extends React.Component<ConditionBuilderProps> {
removeable={false}
onDragStart={this.handleDragStart}
showNot={showNot}
data={data}
/>
);
}

View File

@ -1,3 +1,6 @@
import {SchemaApi} from '../../Schema';
import {Api} from '../../types';
export type FieldTypes =
| 'text'
| 'number'
@ -105,6 +108,8 @@ interface NumberField extends BaseField {
type: 'number';
maximum?: number;
minimum?: number;
step?: number;
precision?: number;
}
interface DateField extends BaseField {
@ -138,6 +143,8 @@ interface SelectField extends BaseField {
name: string;
multiple?: boolean;
options?: Array<any>;
source?: SchemaApi;
searchable?: boolean;
}
interface BooleanField extends BaseField {

111
src/env.tsx Normal file
View File

@ -0,0 +1,111 @@
/**
* @file Env ajax
*/
import React from 'react';
import Alert from './components/Alert2';
import {RendererConfig} from './factory';
import {ThemeInstance} from './theme';
import {Action, Api, Payload, Schema} from './types';
import hoistNonReactStatic from 'hoist-non-react-statics';
export interface RendererEnv {
fetcher: (api: Api, data?: any, options?: object) => Promise<Payload>;
isCancel: (val: any) => boolean;
notify: (
type: 'error' | 'success',
msg: string,
conf?: {
closeButton?: boolean;
timeout?: number;
}
) => void;
jumpTo: (to: string, action?: Action, ctx?: object) => void;
alert: (msg: string) => void;
confirm: (msg: string, title?: string) => Promise<boolean>;
updateLocation: (location: any, replace?: boolean) => void;
/**
* form
*
*
*
*/
blockRouting?: (fn: (targetLocation: any) => void | string) => () => void;
isCurrentUrl: (link: string, ctx?: any) => boolean | {params?: object};
/**
* jssdk
*/
watchRouteChange?: (fn: () => void) => () => void;
rendererResolver?: (
path: string,
schema: Schema,
props: any
) => null | RendererConfig;
copy?: (contents: string) => void;
getModalContainer?: () => HTMLElement;
theme: ThemeInstance;
affixOffsetTop: number;
affixOffsetBottom: number;
richTextToken: string;
loadRenderer: (
schema: Schema,
path: string,
reRender: Function
) => Promise<React.ReactType> | React.ReactType | JSX.Element | void;
[propName: string]: any;
}
export const EnvContext = React.createContext<RendererEnv | void>(undefined);
export interface EnvProps {
env: RendererEnv;
}
export function withRendererEnv<
T extends React.ComponentType<React.ComponentProps<T> & EnvProps>
>(ComposedComponent: T) {
type OuterProps = JSX.LibraryManagedAttributes<
T,
Omit<React.ComponentProps<T>, keyof EnvProps>
> & {
env?: RendererEnv;
};
const result = hoistNonReactStatic(
class extends React.Component<OuterProps> {
static displayName = `WithEnv(${
ComposedComponent.displayName || ComposedComponent.name
})`;
static contextType = EnvContext;
static ComposedComponent = ComposedComponent;
render() {
const injectedProps: {
env: RendererEnv;
} = {
env: this.props.env || this.context
};
if (!injectedProps.env) {
throw new Error('Env 信息获取失败,组件用法不正确');
}
return (
<ComposedComponent
{...(this.props as JSX.LibraryManagedAttributes<
T,
React.ComponentProps<T>
>)}
{...injectedProps}
/>
);
}
},
ComposedComponent
);
return result as typeof result & {
ComposedComponent: T;
};
}

View File

@ -16,6 +16,7 @@ import {alert, confirm, setRenderSchemaFn} from './components/Alert';
import {getDefaultLocale, makeTranslator, LocaleProps} from './locale';
import ScopedRootRenderer, {RootRenderProps} from './Root';
import {HocStoreFactory} from './WithStore';
import {EnvContext, RendererEnv} from './env';
export interface TestFunc {
(
@ -45,54 +46,6 @@ export interface RendererBasicConfig {
// [propName:string]:any;
}
export interface RendererEnv {
fetcher: (api: Api, data?: any, options?: object) => Promise<Payload>;
isCancel: (val: any) => boolean;
notify: (
type: 'error' | 'success',
msg: string,
conf?: {
closeButton?: boolean;
timeout?: number;
}
) => void;
jumpTo: (to: string, action?: Action, ctx?: object) => void;
alert: (msg: string) => void;
confirm: (msg: string, title?: string) => Promise<boolean>;
updateLocation: (location: any, replace?: boolean) => void;
/**
* form
*
*
*
*/
blockRouting?: (fn: (targetLocation: any) => void | string) => () => void;
isCurrentUrl: (link: string, ctx?: any) => boolean | {params?: object};
/**
* jssdk
*/
watchRouteChange?: (fn: () => void) => () => void;
rendererResolver?: (
path: string,
schema: Schema,
props: any
) => null | RendererConfig;
copy?: (contents: string) => void;
getModalContainer?: () => HTMLElement;
theme: ThemeInstance;
affixOffsetTop: number;
affixOffsetBottom: number;
richTextToken: string;
loadRenderer: (
schema: Schema,
path: string,
reRender: Function
) => Promise<React.ReactType> | React.ReactType | JSX.Element | void;
[propName: string]: any;
}
export interface RendererProps extends ThemeProps, LocaleProps {
render: (region: string, node: SchemaNode, props?: any) => JSX.Element;
env: RendererEnv;
@ -402,16 +355,18 @@ export function render(
}
return (
<ScopedRootRenderer
{...props}
schema={schema}
pathPrefix={pathPrefix}
rootStore={store}
env={env}
theme={theme}
locale={locale}
translate={translate}
/>
<EnvContext.Provider value={env}>
<ScopedRootRenderer
{...props}
schema={schema}
pathPrefix={pathPrefix}
rootStore={store}
env={env}
theme={theme}
locale={locale}
translate={translate}
/>
</EnvContext.Provider>
);
}
@ -535,3 +490,5 @@ setRenderSchemaFn((controls, value, callback, scopeRef, theme) => {
}
);
});
export {RendererEnv};

View File

@ -16,9 +16,7 @@ interface ClassDictionary {
[id: string]: any;
}
// This is the only way I found to break circular references between ClassArray and ClassValue
// https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540
interface ClassArray extends Array<ClassValue> {} // tslint:disable-line no-empty-interface
interface ClassArray extends Array<ClassValue> {}
export type ClassNamesFn = (...classes: ClassValue[]) => string;