mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 10:59:42 +08:00
条件组合控件支持远程拉取 fields 配置 (#1758)
This commit is contained in:
parent
aaa554b893
commit
c03c943ae6
@ -355,3 +355,23 @@ type Value = ValueGroup;
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 字段选项远程拉取
|
||||
|
||||
- 方式 1 配置 `source` 接口返回的数据对象 `data` 中存在 fields 变量即可。
|
||||
- 方式 2 关联上下文变量如 `source: "${xxxxField}"`
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "condition-builder",
|
||||
"label": "条件组件",
|
||||
"name": "conditions",
|
||||
"description": "适合让用户自己拼查询条件,然后后端根据数据生成 query where",
|
||||
"source": "/api/mock2/condition-fields?a=${a}&waitSeconds=2"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
@ -27,7 +27,7 @@ import {Api} from '../types';
|
||||
import {LocaleProps, localeable} from '../locale';
|
||||
import Spinner from './Spinner';
|
||||
import {Option, Options} from '../Schema';
|
||||
import {withRemoteOptions} from './WithRemoteOptions';
|
||||
import {RemoteOptionsProps, withRemoteConfig} from './WithRemoteConfig';
|
||||
|
||||
export {Option, Options};
|
||||
|
||||
@ -1012,7 +1012,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
}
|
||||
}
|
||||
|
||||
const enhancedSelect = themeable(
|
||||
const EnhancedSelect = themeable(
|
||||
localeable(
|
||||
uncontrollable(Select, {
|
||||
value: 'onChange'
|
||||
@ -1020,13 +1020,32 @@ const enhancedSelect = themeable(
|
||||
)
|
||||
);
|
||||
|
||||
export default enhancedSelect;
|
||||
export const SelectWithRemoteOptions = withRemoteOptions(
|
||||
enhancedSelect
|
||||
) as React.ComponentType<
|
||||
React.ComponentProps<typeof enhancedSelect> & {
|
||||
source?: any;
|
||||
options?: Options;
|
||||
data?: any;
|
||||
export default EnhancedSelect;
|
||||
export const SelectWithRemoteOptions = withRemoteConfig<Array<Options>>({
|
||||
adaptor: data => data.options || data.items || data.rows || data,
|
||||
normalizeConfig: (options: any, origin) => {
|
||||
options = normalizeOptions(options);
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
return options.concat();
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
>;
|
||||
})(
|
||||
class extends React.Component<
|
||||
RemoteOptionsProps<Array<Options>> &
|
||||
React.ComponentProps<typeof EnhancedSelect>
|
||||
> {
|
||||
render() {
|
||||
const {loading, config, ...rest} = this.props;
|
||||
return (
|
||||
<EnhancedSelect
|
||||
{...rest}
|
||||
options={config || rest.options || []}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
213
src/components/WithRemoteConfig.tsx
Normal file
213
src/components/WithRemoteConfig.tsx
Normal file
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 一个可以拉取远程配置的 HOC
|
||||
*
|
||||
*/
|
||||
import React from 'react';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
import {Api, Payload} from '../types';
|
||||
import {SchemaApi, SchemaTokenizeableString} from '../Schema';
|
||||
import {withStore} from './WithStore';
|
||||
|
||||
import {EnvContext, RendererEnv} from '../env';
|
||||
|
||||
import {flow, Instance, types} from 'mobx-state-tree';
|
||||
import {buildApi, isEffectiveApi, normalizeApi} from '../utils/api';
|
||||
import {
|
||||
isPureVariable,
|
||||
resolveVariableAndFilter,
|
||||
tokenize
|
||||
} from '../utils/tpl-builtin';
|
||||
import {reaction} from 'mobx';
|
||||
|
||||
export const Store = types
|
||||
.model('OptionsStore')
|
||||
.props({
|
||||
fetching: false,
|
||||
errorMsg: '',
|
||||
config: types.frozen(),
|
||||
data: types.frozen({})
|
||||
})
|
||||
.actions(self => {
|
||||
const load: (
|
||||
env: RendererEnv,
|
||||
api: Api,
|
||||
data: any,
|
||||
config: WithRemoteConfigSettings
|
||||
) => Promise<any> = flow(function* (env, api, data, config = {}) {
|
||||
try {
|
||||
self.fetching = true;
|
||||
const ret: Payload = yield env.fetcher(api, data);
|
||||
|
||||
if (ret.ok) {
|
||||
const data = ret.data || {};
|
||||
let options = config.adaptor ? config.adaptor(data) : data;
|
||||
(self as any).setConfig(options, config);
|
||||
} 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 || {};
|
||||
},
|
||||
setConfig(options: any, config: WithRemoteConfigSettings) {
|
||||
if (config.normalizeConfig) {
|
||||
options = config.normalizeConfig(options, self.config) || options;
|
||||
}
|
||||
|
||||
self.config = options;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export type IStore = Instance<typeof Store>;
|
||||
|
||||
export interface OutterProps {
|
||||
env?: RendererEnv;
|
||||
data: any;
|
||||
source?: SchemaApi | SchemaTokenizeableString;
|
||||
}
|
||||
|
||||
export interface RemoteOptionsProps<T = any> {
|
||||
config: T;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export interface WithRemoteConfigSettings {
|
||||
configField?: string;
|
||||
adaptor?: (json: any) => any;
|
||||
normalizeConfig?: (config: any, origin: any) => any;
|
||||
}
|
||||
|
||||
export function withRemoteConfig<P = any>(
|
||||
config: WithRemoteConfigSettings = {}
|
||||
) {
|
||||
return function <
|
||||
T extends React.ComponentType<
|
||||
React.ComponentProps<T> & RemoteOptionsProps<P>
|
||||
>
|
||||
>(ComposedComponent: T) {
|
||||
type FinalOutterProps = JSX.LibraryManagedAttributes<
|
||||
T,
|
||||
Omit<React.ComponentProps<T>, keyof RemoteOptionsProps<P>>
|
||||
> &
|
||||
OutterProps;
|
||||
|
||||
const result = hoistNonReactStatic(
|
||||
withStore(() => Store.create())(
|
||||
class extends React.Component<
|
||||
FinalOutterProps & {
|
||||
store: IStore;
|
||||
}
|
||||
> {
|
||||
static displayName = `WithRemoteConfig(${
|
||||
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} = this.props;
|
||||
|
||||
store.setData(data);
|
||||
|
||||
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(
|
||||
() => {
|
||||
const api = normalizeApi(source as string);
|
||||
return api.trackExpression
|
||||
? tokenize(api.trackExpression, store.data)
|
||||
: buildApi(api, 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} = this.props;
|
||||
|
||||
if (env && isEffectiveApi(source, data)) {
|
||||
store.load(env, source, data, config);
|
||||
}
|
||||
}
|
||||
|
||||
syncOptions() {
|
||||
const {store, source, data} = this.props;
|
||||
|
||||
if (isPureVariable(source)) {
|
||||
store.setConfig(
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || [],
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const store = this.props.store;
|
||||
const injectedProps: RemoteOptionsProps<P> = {
|
||||
config: store.config,
|
||||
loading: store.fetching
|
||||
};
|
||||
|
||||
return (
|
||||
<ComposedComponent
|
||||
{...(this.props as JSX.LibraryManagedAttributes<
|
||||
T,
|
||||
React.ComponentProps<T>
|
||||
>)}
|
||||
{...injectedProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
ComposedComponent
|
||||
);
|
||||
|
||||
return result as typeof result & {
|
||||
ComposedComponent: T;
|
||||
};
|
||||
};
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
/**
|
||||
* 让选项类的组件支持远程加载选项。
|
||||
*
|
||||
* 目前这个逻辑其实在 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;
|
||||
};
|
||||
}
|
@ -40,6 +40,7 @@ export interface ExpressionProps extends ThemeProps {
|
||||
allowedTypes?: Array<'value' | 'field' | 'func' | 'formula'>;
|
||||
op?: OperatorType;
|
||||
config: Config;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const fieldMap = {
|
||||
@ -126,7 +127,8 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||
op,
|
||||
classnames: cx,
|
||||
config,
|
||||
data
|
||||
data,
|
||||
disabled
|
||||
} = this.props;
|
||||
const inputType =
|
||||
((value as any)?.type === 'field'
|
||||
@ -156,6 +158,7 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||
onChange={this.handleValueChange}
|
||||
op={op}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@ -163,6 +166,7 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||
<ConditionField
|
||||
value={(value as any)?.field}
|
||||
onChange={this.handleFieldChange}
|
||||
disabled={disabled}
|
||||
options={
|
||||
valueField
|
||||
? filterTree(
|
||||
@ -184,6 +188,7 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||
funcs={funcs}
|
||||
fields={fields}
|
||||
allowedTypes={allowedTypes}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@ -191,11 +196,13 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||
<Formula
|
||||
value={(value as any)?.value}
|
||||
onChange={this.handleFormulaChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{types.length > 1 ? (
|
||||
<InputSwitch
|
||||
disabled={disabled}
|
||||
value={inputType}
|
||||
onChange={this.handleInputTypeChange}
|
||||
options={types.map(item => ({
|
||||
|
@ -10,6 +10,7 @@ export interface ConditionFieldProps extends ThemeProps {
|
||||
options: Array<any>;
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const option2value = (item: any) => item.name;
|
||||
@ -18,7 +19,8 @@ export function ConditionField({
|
||||
options,
|
||||
onChange,
|
||||
value,
|
||||
classnames: cx
|
||||
classnames: cx,
|
||||
disabled
|
||||
}: ConditionFieldProps) {
|
||||
return (
|
||||
<PopOverContainer
|
||||
@ -45,6 +47,7 @@ export function ConditionField({
|
||||
onResultChange={noop}
|
||||
onResultClick={onClick}
|
||||
placeholder="请选择字段"
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className={cx('CBGroup-fieldCaret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
|
@ -5,15 +5,17 @@ import InputBox from '../InputBox';
|
||||
export interface FormulaProps extends ThemeProps {
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class Formula extends React.Component<FormulaProps> {
|
||||
render() {
|
||||
const {classnames: cx, value, onChange} = this.props;
|
||||
const {classnames: cx, value, onChange, disabled} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx('CBFormula')}>
|
||||
<InputBox
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="请输入公式"
|
||||
|
@ -12,6 +12,7 @@ import {Config} from './config';
|
||||
export interface ConditionFuncProps extends ThemeProps {
|
||||
value: ExpressionFunc;
|
||||
onChange: (value: ExpressionFunc) => void;
|
||||
disabled?: boolean;
|
||||
config: Config;
|
||||
fields?: Field[];
|
||||
funcs?: Funcs;
|
||||
@ -37,7 +38,7 @@ export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||
}
|
||||
|
||||
renderFunc(func: Func) {
|
||||
const {classnames: cx, fields, value, funcs, config} = this.props;
|
||||
const {classnames: cx, fields, value, funcs, config, disabled} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx('CBFunc-args')}>
|
||||
@ -54,6 +55,7 @@ export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||
valueField={{type: item.type} as any}
|
||||
onChange={this.handleArgChange}
|
||||
funcs={funcs}
|
||||
disabled={disabled}
|
||||
// allowedTypes={allowedTypes}
|
||||
/>
|
||||
))}
|
||||
@ -65,7 +67,7 @@ export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, classnames: cx, funcs} = this.props;
|
||||
const {value, classnames: cx, funcs, disabled} = this.props;
|
||||
const func = value
|
||||
? findTree(funcs!, item => (item as Func).type === value.func)
|
||||
: null;
|
||||
@ -97,6 +99,7 @@ export class ConditionFunc extends React.Component<ConditionFuncProps> {
|
||||
onResultChange={noop}
|
||||
onResultClick={onClick}
|
||||
placeholder="请选择字段"
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className={cx('CBGroup-fieldCaret')}>
|
||||
<Icon icon="caret" className="icon" />
|
||||
|
@ -14,6 +14,7 @@ export interface ConditionGroupProps extends ThemeProps {
|
||||
funcs?: Funcs;
|
||||
showNot?: boolean;
|
||||
data?: any;
|
||||
disabled?: boolean;
|
||||
onChange: (value: ConditionGroupValue) => void;
|
||||
removeable?: boolean;
|
||||
onRemove?: (e: React.MouseEvent) => void;
|
||||
@ -119,7 +120,8 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
removeable,
|
||||
onRemove,
|
||||
onDragStart,
|
||||
showNot
|
||||
showNot,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -133,6 +135,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
size="xs"
|
||||
active={value?.not}
|
||||
level={value?.not ? 'info' : 'default'}
|
||||
disabled={disabled}
|
||||
>
|
||||
非
|
||||
</Button>
|
||||
@ -143,6 +146,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
onClick={this.handleConjunctionClick}
|
||||
active={value?.conjunction !== 'or'}
|
||||
level={value?.conjunction !== 'or' ? 'info' : 'default'}
|
||||
disabled={disabled}
|
||||
>
|
||||
并且
|
||||
</Button>
|
||||
@ -151,6 +155,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
onClick={this.handleConjunctionClick}
|
||||
active={value?.conjunction === 'or'}
|
||||
level={value?.conjunction === 'or' ? 'info' : 'default'}
|
||||
disabled={disabled}
|
||||
>
|
||||
或者
|
||||
</Button>
|
||||
@ -158,11 +163,15 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
</div>
|
||||
<div className={cx('CBGroup-toolbarConditionAdd')}>
|
||||
<div className={cx('ButtonGroup')}>
|
||||
<Button onClick={this.handleAdd} size="xs">
|
||||
<Button onClick={this.handleAdd} size="xs" disabled={disabled}>
|
||||
<Icon icon="plus" className="icon" />
|
||||
添加条件
|
||||
</Button>
|
||||
<Button onClick={this.handleAddGroup} size="xs">
|
||||
<Button
|
||||
onClick={this.handleAddGroup}
|
||||
size="xs"
|
||||
disabled={disabled}
|
||||
>
|
||||
<Icon icon="plus-cicle" className="icon" />
|
||||
添加条件组
|
||||
</Button>
|
||||
@ -190,6 +199,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||
funcs={funcs}
|
||||
onRemove={this.handleItemRemove}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
@ -15,6 +15,7 @@ export interface CBGroupOrItemProps extends ThemeProps {
|
||||
index: number;
|
||||
data?: any;
|
||||
draggable?: boolean;
|
||||
disabled?: boolean;
|
||||
onChange: (value: ConditionGroupValue, index: number) => void;
|
||||
removeable?: boolean;
|
||||
onDragStart?: (e: React.MouseEvent) => void;
|
||||
@ -41,6 +42,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
|
||||
funcs,
|
||||
draggable,
|
||||
data,
|
||||
disabled,
|
||||
onDragStart
|
||||
} = this.props;
|
||||
|
||||
@ -59,6 +61,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
|
||||
|
||||
{value?.conjunction ? (
|
||||
<ConditionGroup
|
||||
disabled={disabled}
|
||||
onDragStart={onDragStart}
|
||||
config={config}
|
||||
fields={fields}
|
||||
@ -72,6 +75,7 @@ export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
|
||||
) : (
|
||||
<>
|
||||
<ConditionItem
|
||||
disabled={disabled}
|
||||
config={config}
|
||||
fields={fields}
|
||||
value={value as ConditionValue}
|
||||
|
@ -6,6 +6,7 @@ import {ClassNamesFn, themeable, ThemeProps} from '../../theme';
|
||||
|
||||
export interface InputSwitchProps extends ThemeProps {
|
||||
options: Array<any>;
|
||||
disabled?: boolean;
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
}
|
||||
@ -16,7 +17,8 @@ export function InputSwitch({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
classnames: cx
|
||||
classnames: cx,
|
||||
disabled
|
||||
}: InputSwitchProps) {
|
||||
return (
|
||||
<PopOverContainer
|
||||
@ -28,6 +30,7 @@ export function InputSwitch({
|
||||
options={options}
|
||||
value={value}
|
||||
showRadio={false}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
@ -30,6 +30,7 @@ export interface ConditionItemProps extends ThemeProps {
|
||||
index?: number;
|
||||
value: ConditionRule;
|
||||
data?: any;
|
||||
disabled?: boolean;
|
||||
onChange: (value: ConditionRule, index?: number) => void;
|
||||
}
|
||||
|
||||
@ -96,7 +97,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
}
|
||||
|
||||
renderLeft() {
|
||||
const {value, fields, funcs, config} = this.props;
|
||||
const {value, fields, funcs, config, disabled} = this.props;
|
||||
|
||||
return (
|
||||
<Expression
|
||||
@ -105,6 +106,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
value={value.left}
|
||||
onChange={this.handleLeftChange}
|
||||
fields={fields}
|
||||
disabled={disabled}
|
||||
allowedTypes={
|
||||
['field', 'func'].filter(
|
||||
type => type === 'field' || type === 'func'
|
||||
@ -115,7 +117,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
}
|
||||
|
||||
renderOperator() {
|
||||
const {funcs, config, fields, value, classnames: cx} = this.props;
|
||||
const {funcs, config, fields, value, classnames: cx, disabled} = this.props;
|
||||
const left = value?.left;
|
||||
let operators: Array<string> = [];
|
||||
|
||||
@ -168,6 +170,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
result={OperationMap[value?.op as keyof typeof OperationMap]}
|
||||
onResultChange={noop}
|
||||
onResultClick={onClick}
|
||||
disabled={disabled}
|
||||
placeholder="请选择操作"
|
||||
>
|
||||
<span className={cx('CBGroup-operatorCaret')}>
|
||||
@ -221,7 +224,15 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
}
|
||||
|
||||
renderRightWidgets(type: string, op: OperatorType) {
|
||||
const {funcs, value, data, fields, config, classnames: cx} = this.props;
|
||||
const {
|
||||
funcs,
|
||||
value,
|
||||
data,
|
||||
fields,
|
||||
config,
|
||||
classnames: cx,
|
||||
disabled
|
||||
} = this.props;
|
||||
let field = {
|
||||
...config.types[type],
|
||||
type
|
||||
@ -258,6 +269,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
field?.valueTypes ||
|
||||
config.valueTypes || ['value', 'field', 'func', 'formula']
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<span className={cx('CBSeprator')}>~</span>
|
||||
@ -274,6 +286,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
field?.valueTypes ||
|
||||
config.valueTypes || ['value', 'field', 'func', 'formula']
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -293,6 +306,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||
field?.valueTypes ||
|
||||
config.valueTypes || ['value', 'field', 'func', 'formula']
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export interface ValueProps extends ThemeProps, LocaleProps {
|
||||
onChange: (value: any) => void;
|
||||
field: FieldSimple;
|
||||
op?: OperatorType;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class Value extends React.Component<ValueProps> {
|
||||
@ -25,7 +26,8 @@ export class Value extends React.Component<ValueProps> {
|
||||
onChange,
|
||||
op,
|
||||
translate: __,
|
||||
data
|
||||
data,
|
||||
disabled
|
||||
} = this.props;
|
||||
let input: JSX.Element | undefined = undefined;
|
||||
|
||||
@ -35,6 +37,7 @@ export class Value extends React.Component<ValueProps> {
|
||||
value={value ?? field.defaultValue}
|
||||
onChange={onChange}
|
||||
placeholder={field.placeholder}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'number') {
|
||||
@ -47,6 +50,7 @@ export class Value extends React.Component<ValueProps> {
|
||||
precision={field.precision}
|
||||
value={value ?? field.defaultValue}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'date') {
|
||||
@ -58,6 +62,7 @@ export class Value extends React.Component<ValueProps> {
|
||||
value={value ?? field.defaultValue}
|
||||
onChange={onChange}
|
||||
timeFormat=""
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'time') {
|
||||
@ -71,6 +76,7 @@ export class Value extends React.Component<ValueProps> {
|
||||
onChange={onChange}
|
||||
dateFormat=""
|
||||
timeFormat={field.format || 'HH:mm'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'datetime') {
|
||||
@ -82,6 +88,7 @@ export class Value extends React.Component<ValueProps> {
|
||||
value={value ?? field.defaultValue}
|
||||
onChange={onChange}
|
||||
timeFormat={field.timeFormat || 'HH:mm'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'select') {
|
||||
@ -95,11 +102,16 @@ export class Value extends React.Component<ValueProps> {
|
||||
data={data}
|
||||
onChange={onChange}
|
||||
multiple={op === 'select_any_in' || op === 'select_not_any_in'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
} else if (field.type === 'boolean') {
|
||||
input = (
|
||||
<Switch value={value ?? field.defaultValue} onChange={onChange} />
|
||||
<Switch
|
||||
value={value ?? field.defaultValue}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ export interface ConditionBuilderProps extends ThemeProps, LocaleProps {
|
||||
data?: any;
|
||||
onChange: (value: ConditionGroupValue) => void;
|
||||
config?: Config;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class QueryBuilder extends React.Component<ConditionBuilderProps> {
|
||||
@ -197,7 +198,8 @@ export class QueryBuilder extends React.Component<ConditionBuilderProps> {
|
||||
onChange,
|
||||
value,
|
||||
showNot,
|
||||
data
|
||||
data,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
const normalizedValue = Array.isArray(value?.children)
|
||||
@ -228,6 +230,7 @@ export class QueryBuilder extends React.Component<ConditionBuilderProps> {
|
||||
onDragStart={this.handleDragStart}
|
||||
showNot={showNot}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,11 @@ import ColorPicker from '../../components/ColorPicker';
|
||||
import {Funcs, Fields} from '../../components/condition-builder/types';
|
||||
import {Config} from '../../components/condition-builder/config';
|
||||
import ConditionBuilder from '../../components/condition-builder/index';
|
||||
import {SchemaApi, SchemaTokenizeableString} from '../../Schema';
|
||||
import {
|
||||
RemoteOptionsProps,
|
||||
withRemoteConfig
|
||||
} from '../../components/WithRemoteConfig';
|
||||
|
||||
/**
|
||||
* 条件组合控件
|
||||
@ -29,6 +34,11 @@ export interface ConditionBuilderControlSchema extends FormBaseControl {
|
||||
* 其他配置
|
||||
*/
|
||||
config?: Config;
|
||||
|
||||
/**
|
||||
* 通过远程拉取配置项
|
||||
*/
|
||||
source?: SchemaApi | SchemaTokenizeableString;
|
||||
}
|
||||
|
||||
export interface ConditionBuilderProps
|
||||
@ -44,13 +54,33 @@ export default class ConditionBuilderControl extends React.PureComponent<Conditi
|
||||
|
||||
return (
|
||||
<div className={cx(`ConditionBuilderControl`, className)}>
|
||||
<ConditionBuilder {...rest} />
|
||||
<ConditionBuilderWithRemoteOptions {...rest} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ConditionBuilderWithRemoteOptions = withRemoteConfig({
|
||||
adaptor: data => data.fields || data
|
||||
})(
|
||||
class extends React.Component<
|
||||
RemoteOptionsProps & React.ComponentProps<typeof ConditionBuilder>
|
||||
> {
|
||||
render() {
|
||||
const {loading, config, ...rest} = this.props;
|
||||
return (
|
||||
<ConditionBuilder
|
||||
{...rest}
|
||||
fields={config || rest.fields}
|
||||
disabled={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@FormItem({
|
||||
type: 'condition-builder'
|
||||
type: 'condition-builder',
|
||||
strictMode: false
|
||||
})
|
||||
export class ConditionBuilderRenderer extends ConditionBuilderControl {}
|
||||
|
Loading…
Reference in New Issue
Block a user