mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
feat:支持condition-builder条件运算 (#6430)
* feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算 * feat:支持condition-builder条件运算
This commit is contained in:
parent
dc18814fb5
commit
388a59d06d
@ -2062,7 +2062,7 @@ registerAction('my-action', new MyAction());
|
||||
|
||||
## 条件
|
||||
|
||||
通过配置`expression: 表达式`来实现条件逻辑。
|
||||
通过配置`expression: 表达式或ConditionBuilder组合条件`来实现条件逻辑。
|
||||
|
||||
```schema
|
||||
{
|
||||
@ -2071,7 +2071,15 @@ registerAction('my-action', new MyAction());
|
||||
type: 'form',
|
||||
wrapWithPanel: false,
|
||||
data: {
|
||||
expression: 'okk'
|
||||
expression: 'okk',
|
||||
name: 'amis',
|
||||
features: ['flexible', 'powerful'],
|
||||
tool: 'amis-editor',
|
||||
platform: 'aisuda',
|
||||
detail: {
|
||||
version: '2.8.0',
|
||||
github: 'https://github.com/baidu/amis'
|
||||
}
|
||||
},
|
||||
body: [
|
||||
{
|
||||
@ -2085,7 +2093,7 @@ registerAction('my-action', new MyAction());
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msgType: 'success',
|
||||
msg: '我okk~'
|
||||
msg: 'expression表达式 ok~'
|
||||
},
|
||||
expression: 'expression === "okk"'
|
||||
},
|
||||
@ -2100,9 +2108,32 @@ registerAction('my-action', new MyAction());
|
||||
actionType: 'toast',
|
||||
args: {
|
||||
msgType: 'success',
|
||||
msg: '我也okk~'
|
||||
msg: 'conditin-builder条件组合 也ok~'
|
||||
},
|
||||
expression: 'expression === "okk"'
|
||||
expression: {
|
||||
id: 'b6434ead40cc',
|
||||
conjunction: 'and',
|
||||
children: [
|
||||
{
|
||||
id: 'e92b93840f37',
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'name'
|
||||
},
|
||||
op: 'equal',
|
||||
right: 'amis'
|
||||
},
|
||||
{
|
||||
id: '3779845521db',
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'features'
|
||||
},
|
||||
op: 'select_any_in',
|
||||
right: '${[LAST(features)]}'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -2825,13 +2856,13 @@ http 请求动作执行结束后,后面的动作可以通过 `${responseResult
|
||||
|
||||
# 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------- | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| actionType | `string` | - | 动作名称 |
|
||||
| args | `object` | - | 动作属性`{key:value}`,支持数据映射 |
|
||||
| data | `object` | - | 追加数据`{key:value}`,支持数据映射,如果是触发其他组件的动作,则该数据会传递给目标组件,`> 2.3.2 及以上版本` |
|
||||
| dataMergeMode | `string` | 'merge' | 当配置了 data 的时候,可以控制数据追加方式,支持合并(`merge`)和覆盖(`override`)两种模式,`> 2.3.2 及以上版本` |
|
||||
| preventDefault | `boolean`\|[表达式](../concepts/expression) | false | 阻止事件默认行为,`> 1.10.0 及以上版本支持表达式` |
|
||||
| stopPropagation | `boolean`\|[表达式](../concepts/expression) | false | 停止后续动作执行,`> 1.10.0 及以上版本支持表达式` |
|
||||
| expression | `boolean`\|[表达式](../concepts/expression) | - | 执行条件,不设置表示默认执行 |
|
||||
| outputVar | `string` | - | 输出数据变量名 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| actionType | `string` | - | 动作名称 |
|
||||
| args | `object` | - | 动作属性`{key:value}`,支持数据映射 |
|
||||
| data | `object` | - | 追加数据`{key:value}`,支持数据映射,如果是触发其他组件的动作,则该数据会传递给目标组件,`> 2.3.2 及以上版本` |
|
||||
| dataMergeMode | `string` | 'merge' | 当配置了 data 的时候,可以控制数据追加方式,支持合并(`merge`)和覆盖(`override`)两种模式,`> 2.3.2 及以上版本` |
|
||||
| preventDefault | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | false | 阻止事件默认行为,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` |
|
||||
| stopPropagation | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | false | 停止后续动作执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` |
|
||||
| expression | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | - | 执行条件,不设置表示默认执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` |
|
||||
| outputVar | `string` | - | 输出数据变量名 |
|
||||
|
506
packages/amis-core/__tests__/condition.test.ts
Normal file
506
packages/amis-core/__tests__/condition.test.ts
Normal file
@ -0,0 +1,506 @@
|
||||
import moment from 'moment';
|
||||
import {resolveCondition, guid, registerConditionComputer} from '../src/utils/';
|
||||
|
||||
const data = {
|
||||
name: 'amis',
|
||||
feature: 'flexible',
|
||||
features: ['flexible', 'powerful'],
|
||||
tool: 'amis-editor',
|
||||
platform: 'aisuda',
|
||||
version: '2.9.0',
|
||||
num: 1,
|
||||
num2: 0,
|
||||
num3: undefined,
|
||||
str: '',
|
||||
obj: {},
|
||||
arr: [],
|
||||
bool: true,
|
||||
detail: {
|
||||
version: '2.8.0',
|
||||
github: 'https://github.com/baidu/amis'
|
||||
},
|
||||
range: 1678723200,
|
||||
date: '2023-03-19',
|
||||
datetime: '2023-03-20T04:55:00+08:00',
|
||||
time: '00:05'
|
||||
};
|
||||
|
||||
const equal1 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'name'
|
||||
},
|
||||
op: 'equal',
|
||||
right: '${name}'
|
||||
};
|
||||
const equal2 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'detail'
|
||||
},
|
||||
op: 'equal',
|
||||
right: '${detail}'
|
||||
};
|
||||
const equal3 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'bool'
|
||||
},
|
||||
op: 'equal',
|
||||
right: '${!num2}'
|
||||
};
|
||||
const not_equal = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'version'
|
||||
},
|
||||
op: 'not_equal',
|
||||
right: '${version}'
|
||||
};
|
||||
const greater = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'greater',
|
||||
right: '${num + 0.5}'
|
||||
};
|
||||
const less = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'less',
|
||||
right: '${num - 0.5}'
|
||||
};
|
||||
const greater_or_equal = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'greater_or_equal',
|
||||
right: '${num}'
|
||||
};
|
||||
const less_or_equal = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'less_or_equal',
|
||||
right: '${num}'
|
||||
};
|
||||
const starts_with = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'tool'
|
||||
},
|
||||
op: 'starts_with',
|
||||
right: 'amis-'
|
||||
};
|
||||
const ends_with = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'tool'
|
||||
},
|
||||
op: 'ends_with',
|
||||
right: '-editor'
|
||||
};
|
||||
const like = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'tool'
|
||||
},
|
||||
op: 'like',
|
||||
right: '${tool}-core'
|
||||
};
|
||||
const not_like = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'tool'
|
||||
},
|
||||
op: 'not_like',
|
||||
right: '${tool}r'
|
||||
};
|
||||
const is_empty1 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num2'
|
||||
},
|
||||
op: 'is_empty'
|
||||
};
|
||||
const is_empty2 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'str'
|
||||
},
|
||||
op: 'is_empty'
|
||||
};
|
||||
const is_empty3 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'obj'
|
||||
},
|
||||
op: 'is_empty'
|
||||
};
|
||||
const is_not_empty1 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num3'
|
||||
},
|
||||
op: 'is_not_empty'
|
||||
};
|
||||
const is_not_empty2 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'name'
|
||||
},
|
||||
op: 'is_not_empty'
|
||||
};
|
||||
const is_not_empty3 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'detail'
|
||||
},
|
||||
op: 'is_not_empty'
|
||||
};
|
||||
const is_not_empty4 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'arr'
|
||||
},
|
||||
op: 'is_not_empty'
|
||||
};
|
||||
const between = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'between',
|
||||
right: '${[0.5,1]}'
|
||||
};
|
||||
const not_between = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'range'
|
||||
},
|
||||
op: 'not_between',
|
||||
right: '${[1678636800,1678895999]}'
|
||||
};
|
||||
const select_any_in1 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'features'
|
||||
},
|
||||
op: 'select_any_in',
|
||||
right: '${[LAST(features)]}'
|
||||
};
|
||||
const select_any_in2 = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'feature'
|
||||
},
|
||||
op: 'select_any_in',
|
||||
right: '${features}'
|
||||
};
|
||||
const select_not_any_in = {
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'features'
|
||||
},
|
||||
op: 'select_not_any_in',
|
||||
right: "${['powerful']}"
|
||||
};
|
||||
|
||||
const conditions1 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [equal1, equal2, equal3, not_equal]
|
||||
};
|
||||
|
||||
const conditions2 = {
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [greater, less, greater_or_equal, less_or_equal]
|
||||
};
|
||||
|
||||
const conditions3 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [starts_with, ends_with, like, not_like]
|
||||
};
|
||||
|
||||
const conditions4 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [is_empty1, is_empty2, is_empty3]
|
||||
};
|
||||
|
||||
const conditions5 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [is_not_empty1, is_not_empty2, is_not_empty3, is_not_empty4]
|
||||
};
|
||||
|
||||
const conditions6 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [between, not_between]
|
||||
};
|
||||
|
||||
const conditions7 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [select_any_in1, select_any_in2, select_not_any_in]
|
||||
};
|
||||
|
||||
test(`condition`, async () => {
|
||||
expect(await resolveCondition(conditions1, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions2, data)).toBe(true);
|
||||
expect(await resolveCondition(conditions3, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions4, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions5, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions6, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions7, data)).toBe(false);
|
||||
});
|
||||
|
||||
test(`condition date`, async () => {
|
||||
const conditions = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'date'
|
||||
},
|
||||
op: 'less',
|
||||
right: '2023-03-20'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'datetime'
|
||||
},
|
||||
op: 'less',
|
||||
right: '2023-03-21T04:55:00+08:00'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'time'
|
||||
},
|
||||
op: 'less',
|
||||
right: '00:08'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'date'
|
||||
},
|
||||
op: 'less_or_equal',
|
||||
right: '2023-03-19'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'datetime'
|
||||
},
|
||||
op: 'less_or_equal',
|
||||
right: '2023-03-20T04:55:00+08:00'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'time'
|
||||
},
|
||||
op: 'less_or_equal',
|
||||
right: '00:05'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'date'
|
||||
},
|
||||
op: 'between',
|
||||
right: '${["2023-03-19","2023-03-20"]}'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'datetime'
|
||||
},
|
||||
op: 'between',
|
||||
right: '${["2023-03-20T04:55:00+08:00","2023-03-21T04:55:00+08:00"]}'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'time'
|
||||
},
|
||||
op: 'between',
|
||||
right: '${["00:05","00:06"]}'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(await resolveCondition(conditions, data)).toBe(true);
|
||||
});
|
||||
|
||||
test(`condition tree`, async () => {
|
||||
const conditions8 = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [
|
||||
select_any_in1,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [
|
||||
equal1,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [between, not_between]
|
||||
},
|
||||
between,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [between, select_any_in2, conditions5]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const conditions9 = {
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
is_empty1,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
is_empty1,
|
||||
conditions6,
|
||||
between,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [is_empty1, select_any_in2, conditions5]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const conditions10 = {
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
conditions6,
|
||||
is_empty1,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
is_empty1,
|
||||
{
|
||||
id: guid(),
|
||||
conjunction: 'or',
|
||||
children: [
|
||||
is_not_empty1,
|
||||
is_not_empty2,
|
||||
is_not_empty3,
|
||||
is_not_empty4
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
is_empty1
|
||||
]
|
||||
};
|
||||
|
||||
expect(await resolveCondition(conditions8, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions9, data)).toBe(true);
|
||||
expect(await resolveCondition(conditions10, data)).toBe(true);
|
||||
});
|
||||
|
||||
test(`condition register`, async () => {
|
||||
registerConditionComputer(
|
||||
'customless',
|
||||
(left: any, right?: any, fieldType?: string) => {
|
||||
if (fieldType === 'date') {
|
||||
return moment(left).isBefore(moment(right), 'day');
|
||||
}
|
||||
return left < right;
|
||||
}
|
||||
);
|
||||
|
||||
const conditions = {
|
||||
id: guid(),
|
||||
conjunction: 'and',
|
||||
children: [
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'date',
|
||||
field: 'date'
|
||||
},
|
||||
op: 'customless',
|
||||
right: '2023-03-20'
|
||||
},
|
||||
{
|
||||
id: guid(),
|
||||
left: {
|
||||
type: 'field',
|
||||
field: 'num'
|
||||
},
|
||||
op: 'customless',
|
||||
right: '5'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(await resolveCondition(conditions, data)).toBe(true);
|
||||
});
|
@ -1,8 +1,9 @@
|
||||
import omit from 'lodash/omit';
|
||||
import {RendererProps} from '../factory';
|
||||
import {ConditionGroupValue} from '../types';
|
||||
import {createObject} from '../utils/helper';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {evalExpression} from '../utils/tpl';
|
||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
import {IBreakAction} from './BreakAction';
|
||||
import {IContinueAction} from './ContinueAction';
|
||||
@ -28,7 +29,7 @@ export interface ListenerAction {
|
||||
outputVar?: string; // 输出数据变量名
|
||||
preventDefault?: boolean; // 阻止原有组件的动作行为
|
||||
stopPropagation?: boolean; // 阻止后续的事件处理器执行
|
||||
expression?: string; // 执行条件
|
||||
expression?: string | ConditionGroupValue; // 执行条件
|
||||
execOn?: string; // 执行条件,1.9.0废弃
|
||||
}
|
||||
|
||||
@ -207,18 +208,38 @@ export const runAction = async (
|
||||
);
|
||||
// 兼容一下1.9.0之前的版本
|
||||
const expression = actionConfig.expression ?? actionConfig.execOn;
|
||||
// 执行条件
|
||||
let isStop = false;
|
||||
|
||||
if (expression && !evalExpression(expression, mergeData)) {
|
||||
if (expression) {
|
||||
isStop = !(await evalExpressionWithConditionBuilder(
|
||||
expression,
|
||||
mergeData,
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
if (isStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 支持表达式 >=1.10.0
|
||||
const preventDefault =
|
||||
actionConfig.preventDefault &&
|
||||
evalExpression(String(actionConfig.preventDefault), mergeData);
|
||||
const stopPropagation =
|
||||
actionConfig.stopPropagation &&
|
||||
evalExpression(String(actionConfig.stopPropagation), mergeData);
|
||||
let preventDefault = false;
|
||||
if (actionConfig.preventDefault) {
|
||||
preventDefault = await evalExpressionWithConditionBuilder(
|
||||
String(actionConfig.preventDefault),
|
||||
mergeData,
|
||||
false
|
||||
);
|
||||
}
|
||||
let stopPropagation = false;
|
||||
if (actionConfig.stopPropagation) {
|
||||
stopPropagation = await evalExpressionWithConditionBuilder(
|
||||
String(actionConfig.stopPropagation),
|
||||
mergeData,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// 动作配置
|
||||
const args = dataMapping(actionConfig.args, mergeData, key =>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { RendererEvent } from '../utils/renderer-event';
|
||||
import { evalExpression } from '../utils/tpl';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
||||
import {
|
||||
RendererAction,
|
||||
ListenerContext,
|
||||
@ -27,7 +27,12 @@ export class SwitchAction implements RendererAction {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evalExpression(branch.expression, mergeData)) {
|
||||
const isPass = await evalExpressionWithConditionBuilder(
|
||||
branch.expression,
|
||||
mergeData
|
||||
);
|
||||
|
||||
if (isPass) {
|
||||
await runActions(branch, renderer, event);
|
||||
// 去掉runAllMatch,这里只做排他,多个可以直接通过expression
|
||||
break;
|
||||
|
@ -79,7 +79,9 @@ import type {RendererEnv} from './env';
|
||||
import React from 'react';
|
||||
import {
|
||||
evaluate,
|
||||
evaluateForAsync,
|
||||
Evaluator,
|
||||
AsyncEvaluator,
|
||||
extendsFilters,
|
||||
filters,
|
||||
getFilters,
|
||||
@ -152,6 +154,7 @@ export {
|
||||
parse,
|
||||
lexer,
|
||||
Evaluator,
|
||||
AsyncEvaluator,
|
||||
FilterContext,
|
||||
filters,
|
||||
getFilters,
|
||||
@ -159,6 +162,7 @@ export {
|
||||
extendsFilters,
|
||||
registerFunction,
|
||||
evaluate,
|
||||
evaluateForAsync,
|
||||
// 其他
|
||||
LazyComponent,
|
||||
Overlay,
|
||||
|
@ -626,3 +626,70 @@ export interface BaseSchemaWithoutType {
|
||||
staticInputClassName?: SchemaClassName;
|
||||
staticSchema?: any;
|
||||
}
|
||||
|
||||
export type OperatorType =
|
||||
| 'equal'
|
||||
| 'not_equal'
|
||||
| 'is_empty'
|
||||
| 'is_not_empty'
|
||||
| 'like'
|
||||
| 'not_like'
|
||||
| 'starts_with'
|
||||
| 'ends_with'
|
||||
| 'less'
|
||||
| 'less_or_equal'
|
||||
| 'greater'
|
||||
| 'greater_or_equal'
|
||||
| 'between'
|
||||
| 'not_between'
|
||||
| 'select_equals'
|
||||
| 'select_not_equals'
|
||||
| 'select_any_in'
|
||||
| 'select_not_any_in'
|
||||
| {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ExpressionSimple = string | number | object | undefined;
|
||||
export type ExpressionValue =
|
||||
| ExpressionSimple
|
||||
| {
|
||||
type: 'value';
|
||||
value: ExpressionSimple;
|
||||
};
|
||||
export type ExpressionFunc = {
|
||||
type: 'func';
|
||||
func: string;
|
||||
args: Array<ExpressionComplex>;
|
||||
};
|
||||
export type ExpressionField = {
|
||||
type: 'field';
|
||||
field: string;
|
||||
};
|
||||
export type ExpressionFormula = {
|
||||
type: 'formula';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ExpressionComplex =
|
||||
| ExpressionValue
|
||||
| ExpressionFunc
|
||||
| ExpressionField
|
||||
| ExpressionFormula;
|
||||
|
||||
export interface ConditionRule {
|
||||
id: any;
|
||||
left?: ExpressionComplex;
|
||||
op?: OperatorType;
|
||||
right?: ExpressionComplex | Array<ExpressionComplex>;
|
||||
}
|
||||
|
||||
export interface ConditionGroupValue {
|
||||
id: string;
|
||||
conjunction: 'and' | 'or';
|
||||
not?: boolean;
|
||||
children?: Array<ConditionRule | ConditionGroupValue>;
|
||||
}
|
||||
|
||||
export interface ConditionValue extends ConditionGroupValue {}
|
||||
|
@ -39,6 +39,7 @@ export * from './replaceText';
|
||||
export * from './resize-sensor';
|
||||
export * from './resolveVariable';
|
||||
export * from './resolveVariableAndFilter';
|
||||
export * from './resolveVariableAndFilterForAsync';
|
||||
export * from './RootClose';
|
||||
export * from './scrollPosition';
|
||||
export * from './SimpleMap';
|
||||
@ -52,6 +53,7 @@ export * from './validations';
|
||||
export * from './toNumber';
|
||||
export * from './decodeEntity';
|
||||
export * from './style-helper';
|
||||
export * from './resolveCondition';
|
||||
|
||||
import animation from './Animation';
|
||||
|
||||
|
341
packages/amis-core/src/utils/resolveCondition.ts
Normal file
341
packages/amis-core/src/utils/resolveCondition.ts
Normal file
@ -0,0 +1,341 @@
|
||||
import get from 'lodash/get';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import startsWith from 'lodash/startsWith';
|
||||
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
||||
import moment from 'moment';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
const conditionResolverMap: {
|
||||
[op: string]: (left: any, right: any, fieldType?: string) => boolean;
|
||||
} = {};
|
||||
const DEFAULT_RESULT = true;
|
||||
|
||||
export async function resolveCondition(
|
||||
conditions: any,
|
||||
data: any,
|
||||
defaultResult: boolean = true
|
||||
) {
|
||||
if (
|
||||
!conditions ||
|
||||
!conditions.conjunction ||
|
||||
!Array.isArray(conditions.children) ||
|
||||
!conditions.children.length
|
||||
) {
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
return await computeConditions(
|
||||
conditions.children,
|
||||
conditions.conjunction,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
async function computeConditions(
|
||||
conditions: any[],
|
||||
conjunction: 'or' | 'and' = 'and',
|
||||
data: any
|
||||
): Promise<boolean> {
|
||||
let computeResult = true;
|
||||
for (let index = 0, len = conditions.length; index < len; index++) {
|
||||
const item = conditions[index];
|
||||
const result =
|
||||
item.conjunction && Array.isArray(item.children) && item.children.length
|
||||
? await computeConditions(item.children, item.conjunction, data)
|
||||
: await computeCondition(item, index, data);
|
||||
|
||||
computeResult = !!result;
|
||||
|
||||
if (
|
||||
(result && conjunction === 'or') ||
|
||||
(!result && conjunction === 'and')
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return computeResult;
|
||||
}
|
||||
|
||||
async function computeCondition(
|
||||
rule: {
|
||||
op: string;
|
||||
left: {
|
||||
type: string;
|
||||
field: string;
|
||||
};
|
||||
right: any;
|
||||
},
|
||||
index: number,
|
||||
data: any
|
||||
) {
|
||||
const leftValue = get(data, rule.left.field);
|
||||
const rightValue: any = await resolveVariableAndFilterForAsync(
|
||||
rule.right,
|
||||
data
|
||||
);
|
||||
|
||||
const func =
|
||||
conditionResolverMap[`${rule.op}For${capitalize(rule.left.type)}`] ??
|
||||
conditionResolverMap[rule.op];
|
||||
|
||||
return func ? func(leftValue, rightValue, rule.left.type) : DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function startsWithFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return startsWith(left, right);
|
||||
}
|
||||
|
||||
function endsWithFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return endsWith(left, right);
|
||||
}
|
||||
|
||||
function equalFunc(left: any, right: any) {
|
||||
return isEqual(left, right);
|
||||
}
|
||||
|
||||
function notEqualFunc(left: any, right: any) {
|
||||
return !isEqual(left, right);
|
||||
}
|
||||
|
||||
function isEmptyFunc(left: any) {
|
||||
if (typeof left === 'string') {
|
||||
return !left;
|
||||
} else if (typeof left === 'number') {
|
||||
return left === undefined;
|
||||
} else if (Array.isArray(left)) {
|
||||
return !left.length;
|
||||
} else if (typeof left === 'object') {
|
||||
return isEmpty(left);
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function isNotEmptyFunc(left: any) {
|
||||
if (typeof left === 'string') {
|
||||
return !left;
|
||||
} else if (typeof left === 'number') {
|
||||
return left !== undefined;
|
||||
} else if (Array.isArray(left)) {
|
||||
return !!left.length;
|
||||
} else if (typeof left === 'object') {
|
||||
return !isEmpty(left);
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function greaterFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return parseFloat(left as any) > parseFloat(right as any);
|
||||
}
|
||||
|
||||
function normalizeDate(raw: any): Date {
|
||||
if (typeof raw === 'string' || typeof raw === 'number') {
|
||||
let formats = ['', 'YYYY-MM-DD HH:mm:ss', 'X'];
|
||||
|
||||
if (/^\d{10}((\.\d+)*)$/.test(raw.toString())) {
|
||||
formats = ['X', 'x', 'YYYY-MM-DD HH:mm:ss', ''];
|
||||
} else if (/^\d{13}((\.\d+)*)$/.test(raw.toString())) {
|
||||
formats = ['x', 'X', 'YYYY-MM-DD HH:mm:ss', ''];
|
||||
}
|
||||
while (formats.length) {
|
||||
const format = formats.shift()!;
|
||||
const date = moment(raw, format);
|
||||
|
||||
if (date.isValid()) {
|
||||
return date.toDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
function normalizeDateRange(raw: string | Date[]): Date[] {
|
||||
return (Array.isArray(raw) ? raw : raw.split(',')).map((item: any) =>
|
||||
normalizeDate(String(item).trim())
|
||||
);
|
||||
}
|
||||
|
||||
function greaterForDateFunc(left: any, right: any) {
|
||||
left = normalizeDate(left);
|
||||
right = normalizeDate(right);
|
||||
return moment(left).isAfter(moment(right), 's');
|
||||
}
|
||||
|
||||
function greaterOrEqualForDateFunc(left: any, right: any) {
|
||||
left = normalizeDate(left);
|
||||
right = normalizeDate(right);
|
||||
return moment(left).isSameOrAfter(moment(right), 's');
|
||||
}
|
||||
|
||||
function greaterOrEqualFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return parseFloat(left as any) >= parseFloat(right as any);
|
||||
}
|
||||
|
||||
function lessFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return parseFloat(left as any) < parseFloat(right as any);
|
||||
}
|
||||
|
||||
function lessForDateFunc(left: any, right: any) {
|
||||
left = normalizeDate(left);
|
||||
right = normalizeDate(right);
|
||||
return moment(left).isBefore(moment(right), 's');
|
||||
}
|
||||
|
||||
function lessOrEqualForDateFunc(left: any, right: any) {
|
||||
left = normalizeDate(left);
|
||||
right = normalizeDate(right);
|
||||
return moment(left).isSameOrBefore(moment(right), 's');
|
||||
}
|
||||
|
||||
function lessOrEqualFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return parseFloat(left as any) <= parseFloat(right as any);
|
||||
}
|
||||
|
||||
function likeFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return !!~left.indexOf(right);
|
||||
}
|
||||
|
||||
function notLikeFunc(left: any, right: any) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
return !~left.indexOf(right);
|
||||
}
|
||||
|
||||
function betweenFunc(left: any, right: any) {
|
||||
if (typeof left === 'number' && right !== undefined) {
|
||||
const [min, max] = right.sort();
|
||||
return left >= parseFloat(min) && left <= parseFloat(max);
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function betweenForDateFunc(left: any, right: any) {
|
||||
if (right !== undefined) {
|
||||
const [min, max] = normalizeDateRange(right);
|
||||
return moment(normalizeDate(left)).isBetween(min, max, 's', '[]');
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function notBetweenFunc(left: any, right: any) {
|
||||
if (typeof left === 'number' && right !== undefined) {
|
||||
const [min, max] = right.sort();
|
||||
return left < parseFloat(min) && left > parseFloat(max);
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function notBetweenForDateFunc(left: any, right: any) {
|
||||
if (right !== undefined) {
|
||||
const [min, max] = normalizeDateRange(right);
|
||||
return !moment(normalizeDate(left)).isBetween(min, max, 's', '[]');
|
||||
}
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
function selectAnyInFunc(left: any, right: any) {
|
||||
if (!Array.isArray(right)) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
if (Array.isArray(left)) {
|
||||
return right.every((item: any) => left.includes(item));
|
||||
}
|
||||
return right.includes(left);
|
||||
}
|
||||
|
||||
function selectNotAnyInFunc(left: any, right: any) {
|
||||
if (!Array.isArray(right)) {
|
||||
return DEFAULT_RESULT;
|
||||
}
|
||||
|
||||
if (Array.isArray(left)) {
|
||||
return !right.every((item: any) => left.includes(item));
|
||||
}
|
||||
return !right.includes(left);
|
||||
}
|
||||
|
||||
export function registerConditionComputer(
|
||||
op: string,
|
||||
func: (left: any, right: any, fieldType?: string) => boolean,
|
||||
fieldType?: string
|
||||
) {
|
||||
conditionResolverMap[
|
||||
`${op}${fieldType ? 'For' + capitalize(fieldType) : ''}`
|
||||
] = func;
|
||||
}
|
||||
|
||||
export function getConditionComputers() {
|
||||
return conditionResolverMap;
|
||||
}
|
||||
|
||||
registerConditionComputer('greater', greaterFunc);
|
||||
registerConditionComputer('greater', greaterForDateFunc, 'date');
|
||||
registerConditionComputer('greater', greaterForDateFunc, 'time');
|
||||
registerConditionComputer('greater', greaterForDateFunc, 'datetime');
|
||||
registerConditionComputer('greater_or_equal', greaterOrEqualFunc);
|
||||
registerConditionComputer(
|
||||
'greater_or_equal',
|
||||
greaterOrEqualForDateFunc,
|
||||
'date'
|
||||
);
|
||||
registerConditionComputer(
|
||||
'greater_or_equal',
|
||||
greaterOrEqualForDateFunc,
|
||||
'time'
|
||||
);
|
||||
registerConditionComputer(
|
||||
'greater_or_equal',
|
||||
greaterOrEqualForDateFunc,
|
||||
'datetime'
|
||||
);
|
||||
registerConditionComputer('less', lessFunc);
|
||||
registerConditionComputer('less', lessForDateFunc, 'date');
|
||||
registerConditionComputer('less', lessForDateFunc, 'time');
|
||||
registerConditionComputer('less', lessForDateFunc, 'datetime');
|
||||
registerConditionComputer('less_or_equal', lessOrEqualFunc);
|
||||
registerConditionComputer('less_or_equal', lessOrEqualForDateFunc, 'date');
|
||||
registerConditionComputer('less_or_equal', lessOrEqualForDateFunc, 'time');
|
||||
registerConditionComputer('less_or_equal', lessOrEqualForDateFunc, 'datetime');
|
||||
registerConditionComputer('is_empty', isEmptyFunc);
|
||||
registerConditionComputer('is_not_empty', isNotEmptyFunc);
|
||||
registerConditionComputer('between', betweenFunc);
|
||||
registerConditionComputer('between', betweenForDateFunc, 'date');
|
||||
registerConditionComputer('between', betweenForDateFunc, 'time');
|
||||
registerConditionComputer('between', betweenForDateFunc, 'datetime');
|
||||
registerConditionComputer('not_between', notBetweenFunc);
|
||||
registerConditionComputer('not_between', notBetweenForDateFunc, 'date');
|
||||
registerConditionComputer('not_between', notBetweenForDateFunc, 'time');
|
||||
registerConditionComputer('not_between', notBetweenForDateFunc, 'datetime');
|
||||
registerConditionComputer('equal', equalFunc);
|
||||
registerConditionComputer('not_equal', notEqualFunc);
|
||||
registerConditionComputer('like', likeFunc);
|
||||
registerConditionComputer('not_like', notLikeFunc);
|
||||
registerConditionComputer('select_any_in', selectAnyInFunc);
|
||||
registerConditionComputer('select_not_any_in', selectNotAnyInFunc);
|
||||
registerConditionComputer('starts_with', startsWithFunc);
|
||||
registerConditionComputer('ends_with', endsWithFunc);
|
@ -0,0 +1,30 @@
|
||||
import {AsyncEvaluator, parse} from 'amis-formula';
|
||||
|
||||
export const resolveVariableAndFilterForAsync = async (
|
||||
path?: string,
|
||||
data: object = {},
|
||||
defaultFilter: string = '| html',
|
||||
fallbackValue = (value: any) => value
|
||||
) => {
|
||||
if (!path || typeof path !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const ast = parse(path, {
|
||||
evalMode: false,
|
||||
allowFilter: true
|
||||
});
|
||||
|
||||
const ret = await new AsyncEvaluator(data, {
|
||||
defaultFilter
|
||||
}).evalute(ast);
|
||||
|
||||
return ret == null && !~path.indexOf('default') && !~path.indexOf('now')
|
||||
? fallbackValue(ret)
|
||||
: ret;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import {createObject} from './helper';
|
||||
import {register as registerBulitin, getFilters} from './tpl-builtin';
|
||||
import {register as registerLodash} from './tpl-lodash';
|
||||
import {parse, evaluate} from 'amis-formula';
|
||||
import {resolveCondition} from './resolveCondition';
|
||||
|
||||
export interface Enginer {
|
||||
test: (tpl: string) => boolean;
|
||||
@ -99,6 +99,25 @@ export function evalExpression(expression: string, data?: object): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表达式(支持condition-builder)
|
||||
* @param expression 表达式 or condition-builder对象
|
||||
* @param data 上下文
|
||||
* @returns
|
||||
*/
|
||||
export async function evalExpressionWithConditionBuilder(
|
||||
expression: any,
|
||||
data?: object,
|
||||
defaultResult?: boolean
|
||||
): Promise<boolean> {
|
||||
// 支持ConditionBuilder
|
||||
if (Object.prototype.toString.call(expression) === '[object Object]') {
|
||||
return await resolveCondition(expression, data, defaultResult);
|
||||
}
|
||||
|
||||
return evalExpression(expression, data);
|
||||
}
|
||||
|
||||
const AST_CACHE: {[key: string]: any} = {};
|
||||
function evalFormula(expression: string, data: any) {
|
||||
const ast =
|
||||
|
589
packages/amis-formula/__tests__/async-evalute.test.ts
Normal file
589
packages/amis-formula/__tests__/async-evalute.test.ts
Normal file
@ -0,0 +1,589 @@
|
||||
import {evaluateForAsync, parse} from '../src';
|
||||
|
||||
test('evalute:simple', async () => {
|
||||
expect(
|
||||
await evaluateForAsync('a is ${a}', {
|
||||
a: 123
|
||||
})
|
||||
).toBe('a is 123');
|
||||
});
|
||||
|
||||
test('evalute:filter', async () => {
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${a | abc}',
|
||||
{
|
||||
a: 123
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
abc(input: any) {
|
||||
return `${input}456`;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 123456');
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${a | concat:233}',
|
||||
{
|
||||
a: 123
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
concat(input: any, arg: string) {
|
||||
return `${input}${arg}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 123233');
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${concat(a, a)}',
|
||||
{
|
||||
a: 123
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
concat(input: any, arg: string) {
|
||||
return `${input}${arg}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 123123');
|
||||
});
|
||||
|
||||
test('evalute:filter2', async () => {
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${[1, 2, 3] | concat:4 | join}',
|
||||
{},
|
||||
{
|
||||
filters: {
|
||||
concat(input: any, ...args: Array<any>) {
|
||||
return input.concat.apply(input, args);
|
||||
},
|
||||
join(input: any) {
|
||||
return input.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 1,2,3,4');
|
||||
});
|
||||
|
||||
test('evalute:filter3', async () => {
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${[1, 2, 3] | concat:"4" | join}',
|
||||
{},
|
||||
{
|
||||
filters: {
|
||||
concat(input: any, ...args: Array<any>) {
|
||||
return input.concat.apply(input, args);
|
||||
},
|
||||
join(input: any) {
|
||||
return input.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 1,2,3,4');
|
||||
});
|
||||
|
||||
test('evalute:filter4', async () => {
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'a is ${[1, 2, 3] | concat:${a + 3} | join}',
|
||||
{
|
||||
a: 4
|
||||
},
|
||||
{
|
||||
filters: {
|
||||
concat(input: any, ...args: Array<any>) {
|
||||
return input.concat.apply(input, args);
|
||||
},
|
||||
join(input: any) {
|
||||
return input.join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).toBe('a is 1,2,3,7');
|
||||
});
|
||||
|
||||
test('evalute:oldVariable', async () => {
|
||||
expect(
|
||||
await evaluateForAsync('a is $a', {
|
||||
a: 4
|
||||
})
|
||||
).toBe('a is 4');
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('b is $b', {
|
||||
a: 4
|
||||
})
|
||||
).toBe('b is ');
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('a.b is $a.b', {
|
||||
a: {
|
||||
b: 233
|
||||
}
|
||||
})
|
||||
).toBe('a.b is 233');
|
||||
});
|
||||
|
||||
test('evalute:ariable2', async () => {
|
||||
expect(
|
||||
await evaluateForAsync('a is $$', {
|
||||
a: 4
|
||||
})
|
||||
).toBe('a is [object Object]');
|
||||
});
|
||||
|
||||
test('evalute:ariable3', async () => {
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'$$',
|
||||
{
|
||||
a: 4
|
||||
},
|
||||
{
|
||||
defaultFilter: 'raw'
|
||||
}
|
||||
)
|
||||
).toMatchObject({
|
||||
a: 4
|
||||
});
|
||||
});
|
||||
|
||||
test('evalute:object-variable', async () => {
|
||||
const data = {
|
||||
key: 'x',
|
||||
obj: {
|
||||
x: 1,
|
||||
y: 2
|
||||
}
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('a is ${obj.x}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj[x]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj[`x`]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj["x"]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj[key]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj[`${key}`]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${obj[${key}]}', data)).toBe('a is 1');
|
||||
});
|
||||
|
||||
test('evalute:literal-variable', async () => {
|
||||
const data = {
|
||||
key: 'x',
|
||||
index: 0,
|
||||
obj: {
|
||||
x: 1,
|
||||
y: 2
|
||||
}
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('a is ${({x: 1})["x"]}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${({x: 1}).x}', data)).toBe('a is 1');
|
||||
expect(await evaluateForAsync('a is ${(["a", "b"])[index]}', data)).toBe(
|
||||
'a is a'
|
||||
);
|
||||
expect(await evaluateForAsync('a is ${(["a", "b"])[1]}', data)).toBe(
|
||||
'a is b'
|
||||
);
|
||||
expect(await evaluateForAsync('a is ${(["a", "b"]).0}', data)).toBe('a is a');
|
||||
});
|
||||
|
||||
test('evalute:tempalte', async () => {
|
||||
const data = {
|
||||
key: 'x'
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('abc${`11${3}22`}xyz', data)).toBe(
|
||||
'abc11322xyz'
|
||||
);
|
||||
expect(await evaluateForAsync('abc${`${3}22`}xyz', data)).toBe('abc322xyz');
|
||||
expect(await evaluateForAsync('abc${`11${3}`}xyz', data)).toBe('abc113xyz');
|
||||
expect(await evaluateForAsync('abc${`${3}`}xyz', data)).toBe('abc3xyz');
|
||||
expect(await evaluateForAsync('abc${`${key}`}xyz', data)).toBe('abcxxyz');
|
||||
});
|
||||
|
||||
test('evalute:literal', async () => {
|
||||
const data = {
|
||||
dynamicKey: 'alpha'
|
||||
};
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('${{a: 1, 0: 2, "3": 3}}', data, {
|
||||
defaultFilter: 'raw'
|
||||
})
|
||||
).toMatchObject({
|
||||
a: 1,
|
||||
0: 2,
|
||||
3: 3
|
||||
});
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('${{a: 1, 0: 2, "3": 3, [`4`]: 4}}', data, {
|
||||
defaultFilter: 'raw'
|
||||
})
|
||||
).toMatchObject({
|
||||
a: 1,
|
||||
0: 2,
|
||||
3: 3,
|
||||
4: 4
|
||||
});
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${{a: 1, 0: 2, "3": 3, [`${dynamicKey}233`]: 4}}',
|
||||
data,
|
||||
{
|
||||
defaultFilter: 'raw'
|
||||
}
|
||||
)
|
||||
).toMatchObject({
|
||||
a: 1,
|
||||
0: 2,
|
||||
3: 3,
|
||||
alpha233: 4
|
||||
});
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${[1, 2, `2${dynamicKey}2`, {a: 1, 0: 2, [`2`]: "3"}]}',
|
||||
data,
|
||||
{
|
||||
defaultFilter: 'raw'
|
||||
}
|
||||
)
|
||||
).toMatchObject([1, 2, `2alpha2`, {a: 1, 0: 2, [`2`]: '3'}]);
|
||||
});
|
||||
|
||||
test('evalute:variableName', async () => {
|
||||
const data = {
|
||||
'a-b': 'c',
|
||||
'222': 10222,
|
||||
'222_221': 233,
|
||||
'222_abcde': 'abcde',
|
||||
'222-221': 333
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('${a-b}', data)).toBe('c');
|
||||
expect(await evaluateForAsync('${222}', data)).toBe(222);
|
||||
expect(await evaluateForAsync('${222_221}', data)).toBe('233');
|
||||
expect(await evaluateForAsync('${222-221}', data)).toBe(1);
|
||||
expect(await evaluateForAsync('${222_abcde}', data)).toBe('abcde');
|
||||
expect(
|
||||
await evaluateForAsync('${&["222-221"]}', data, {
|
||||
defaultFilter: 'raw'
|
||||
})
|
||||
).toBe(333);
|
||||
expect(
|
||||
await evaluateForAsync('222', data, {
|
||||
variableMode: true
|
||||
})
|
||||
).toBe(10222);
|
||||
});
|
||||
|
||||
test('evalute:3-1', async () => {
|
||||
const data = {};
|
||||
|
||||
expect(await evaluateForAsync('${3-1}', data)).toBe(2);
|
||||
expect(await evaluateForAsync('${-1 + 2.5 + 3}', data)).toBe(4.5);
|
||||
expect(await evaluateForAsync('${-1 + -1}', data)).toBe(-2);
|
||||
expect(await evaluateForAsync('${3 * -1}', data)).toBe(-3);
|
||||
|
||||
expect(await evaluateForAsync('${3 + +1}', data)).toBe(4);
|
||||
});
|
||||
|
||||
test('evalate:0.1+0.2', async () => {
|
||||
expect(await evaluateForAsync('${0.1 + 0.2}', {})).toBe(0.3);
|
||||
});
|
||||
|
||||
test('evalute:variable:com.xxx.xx', async () => {
|
||||
const data = {
|
||||
'com.xxx.xx': 'abc',
|
||||
'com xxx%xx': 'cde',
|
||||
'com[xxx]': 'eee'
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('${com\\.xxx\\.xx}', data)).toBe('abc');
|
||||
expect(await evaluateForAsync('${com\\ xxx\\%xx}', data)).toBe('cde');
|
||||
expect(await evaluateForAsync('${com\\[xxx\\]}', data)).toBe('eee');
|
||||
});
|
||||
|
||||
test('evalute:anonymous:function', async () => {
|
||||
const data = {
|
||||
arr: [1, 2, 3],
|
||||
arr2: [
|
||||
{
|
||||
a: 1
|
||||
},
|
||||
{
|
||||
a: 2
|
||||
},
|
||||
{
|
||||
a: 3
|
||||
}
|
||||
],
|
||||
outter: 4
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('${() => 233}', data)).toMatchObject({
|
||||
args: [],
|
||||
return: {type: 'literal', value: 233},
|
||||
type: 'anonymous_function'
|
||||
});
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('${ARRAYMAP(arr, () => 1)}', data)
|
||||
).toMatchObject([1, 1, 1]);
|
||||
expect(
|
||||
await evaluateForAsync('${ARRAYMAP(arr, item => item)}', data)
|
||||
).toMatchObject([1, 2, 3]);
|
||||
expect(
|
||||
await evaluateForAsync('${ARRAYMAP(arr, item => item * 2)}', data)
|
||||
).toMatchObject([2, 4, 6]);
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYMAP(arr2, (item, index) => `a${item.a}${index}`)}',
|
||||
data
|
||||
)
|
||||
).toMatchObject(['a10', 'a21', 'a32']);
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYMAP(arr2, (item, index) => `a${item.a}${index}${outter}`)}',
|
||||
data
|
||||
)
|
||||
).toMatchObject(['a104', 'a214', 'a324']);
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYMAP(arr2, (item, index) => {x: item.a, index: index})}',
|
||||
data
|
||||
)
|
||||
).toMatchObject([
|
||||
{
|
||||
x: 1,
|
||||
index: 0
|
||||
},
|
||||
{
|
||||
x: 2,
|
||||
index: 1
|
||||
},
|
||||
{
|
||||
x: 3,
|
||||
index: 2
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('evalute:anonymous:function2', async () => {
|
||||
const data = {
|
||||
arr: [1, 2, 3],
|
||||
arr2: [
|
||||
{
|
||||
x: 1,
|
||||
y: [
|
||||
{
|
||||
z: 1
|
||||
},
|
||||
{
|
||||
z: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
x: 2,
|
||||
y: [
|
||||
{
|
||||
z: 2
|
||||
},
|
||||
{
|
||||
z: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYMAP(ARRAYMAP(arr, item => item * 2), item => item + 2)}',
|
||||
data
|
||||
)
|
||||
).toMatchObject([4, 6, 8]);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYMAP(arr2, item => ARRAYMAP(item.y, i => i.z))}',
|
||||
data
|
||||
)
|
||||
).toMatchObject([
|
||||
[1, 1],
|
||||
[2, 2]
|
||||
]);
|
||||
});
|
||||
|
||||
test('evalute:array:func', async () => {
|
||||
const data = {
|
||||
arr1: [0, 1, false, 2, '', 3],
|
||||
arr2: ['a', 'b', 'c'],
|
||||
arr3: [1, 2, 3],
|
||||
arr4: [2, 4, 6],
|
||||
arr5: [
|
||||
{
|
||||
id: 1.1
|
||||
},
|
||||
{
|
||||
id: 2.2
|
||||
},
|
||||
{
|
||||
id: 1.1,
|
||||
name: 1.3
|
||||
}
|
||||
],
|
||||
obj1: {
|
||||
p1: 'name',
|
||||
p2: 'age',
|
||||
p3: 'obj',
|
||||
p4: [
|
||||
{
|
||||
p41: 'Tom',
|
||||
p42: 'Jerry'
|
||||
},
|
||||
{
|
||||
p41: 'baidu',
|
||||
p42: 'amis'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
expect(await evaluateForAsync('${COMPACT(arr1)}', data)).toMatchObject([
|
||||
1, 2, 3
|
||||
]);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync("${COMPACT([0, 1, false, 2, '', 3])}", data)
|
||||
).toMatchObject([1, 2, 3]);
|
||||
|
||||
expect(await evaluateForAsync('${JOIN(arr2, "~")}', data)).toMatch('a~b~c');
|
||||
|
||||
expect(await evaluateForAsync('${SUM(arr3)}', data)).toBe(6);
|
||||
|
||||
expect(await evaluateForAsync('${AVG(arr4)}', data)).toBe(4);
|
||||
|
||||
expect(await evaluateForAsync('${MIN(arr4)}', data)).toBe(2);
|
||||
|
||||
expect(await evaluateForAsync('${MAX(arr4)}', data)).toBe(6);
|
||||
|
||||
expect(await evaluateForAsync('${CONCAT(arr3, arr4)}', data)).toMatchObject([
|
||||
1, 2, 3, 2, 4, 6
|
||||
]);
|
||||
|
||||
expect(await evaluateForAsync('${CONCAT(arr, arr4)}', data)).toMatchObject([
|
||||
2, 4, 6
|
||||
]);
|
||||
|
||||
expect(await evaluateForAsync('${UNIQ(arr5)}', data)).toMatchObject(
|
||||
data.arr5
|
||||
);
|
||||
|
||||
expect(await evaluateForAsync('${UNIQ(arr5, "id")}', data)).toMatchObject([
|
||||
{id: 1.1},
|
||||
{id: 2.2}
|
||||
]);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('${ARRAYFILTER(arr1, item => item)}', data)
|
||||
).toMatchObject([1, 2, 3]);
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYFILTER(arr1, item => item && item >=2)}',
|
||||
data
|
||||
)
|
||||
).toMatchObject([2, 3]);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync('${ARRAYFINDINDEX(arr3, item => item === 2)}', data)
|
||||
).toBe(1);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYFIND(arr5, item => item.name === 1.3)}',
|
||||
data
|
||||
)
|
||||
).toMatchObject({
|
||||
id: 1.1,
|
||||
name: 1.3
|
||||
});
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYSOME(arr5, item => item.name === 1.3)}',
|
||||
data
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
await evaluateForAsync(
|
||||
'${ARRAYEVERY(arr5, item => item.name === 1.3)}',
|
||||
data
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
expect(await evaluateForAsync('${ARRAYINCLUDES(arr1, false)}', data)).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
expect(await evaluateForAsync('${GET(arr1, 2)}', data)).toBe(false);
|
||||
expect(await evaluateForAsync('${GET(arr1, 6, "not-found")}', data)).toBe(
|
||||
'not-found'
|
||||
);
|
||||
expect(await evaluateForAsync('${GET(arr5, "[2].name")}', data)).toBe(1.3);
|
||||
expect(await evaluateForAsync('${GET(arr5, "2.name")}', data)).toBe(1.3);
|
||||
expect(await evaluateForAsync('${GET(obj1, "p2")}', data)).toBe('age');
|
||||
expect(await evaluateForAsync('${GET(obj1, "p4.1.p42")}', data)).toBe('amis');
|
||||
expect(await evaluateForAsync('${GET(obj1, "p4[1].p42")}', data)).toBe(
|
||||
'amis'
|
||||
);
|
||||
|
||||
expect(await evaluateForAsync('${ENCODEJSON(obj1)}', data)).toBe(
|
||||
JSON.stringify(data.obj1)
|
||||
);
|
||||
expect(
|
||||
await evaluateForAsync('${DECODEJSON("{\\"name\\":\\"amis\\"}")}', data)
|
||||
).toMatchObject(JSON.parse('{"name":"amis"}'));
|
||||
});
|
||||
|
||||
test('evalute:ISTYPE', async () => {
|
||||
const data = {
|
||||
a: 1,
|
||||
b: 'string',
|
||||
c: null,
|
||||
d: undefined,
|
||||
e: [1, 2],
|
||||
f: {a: 1, b: 2},
|
||||
g: new Date()
|
||||
};
|
||||
expect(await evaluateForAsync('${ISTYPE(a, "number")}', data)).toBe(true);
|
||||
expect(await evaluateForAsync('${ISTYPE(b, "number")}', data)).toBe(false);
|
||||
expect(await evaluateForAsync('${ISTYPE(b, "string")}', data)).toBe(true);
|
||||
expect(await evaluateForAsync('${ISTYPE(c, "nil")}', data)).toBe(true);
|
||||
expect(await evaluateForAsync('${ISTYPE(d, "nil")}', data)).toBe(true);
|
||||
expect(await evaluateForAsync('${ISTYPE(e, "array")}', data)).toBe(true);
|
||||
expect(await evaluateForAsync('${ISTYPE(f, "array")}', data)).toBe(false);
|
||||
expect(await evaluateForAsync('${ISTYPE(f, "plain-object")}', data)).toBe(
|
||||
true
|
||||
);
|
||||
expect(await evaluateForAsync('${ISTYPE(g, "date")}', data)).toBe(true);
|
||||
});
|
390
packages/amis-formula/__tests__/async-fomula.test.ts
Normal file
390
packages/amis-formula/__tests__/async-fomula.test.ts
Normal file
@ -0,0 +1,390 @@
|
||||
import moment from 'moment';
|
||||
import {evaluateForAsync, registerFunction} from '../src';
|
||||
|
||||
const defaultContext = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4,
|
||||
e: 5
|
||||
};
|
||||
|
||||
async function evalFormual(expression: string, data: any = defaultContext) {
|
||||
return evaluateForAsync(expression, data, {
|
||||
evalMode: true
|
||||
});
|
||||
}
|
||||
|
||||
test('formula:expression', async () => {
|
||||
expect(await evalFormual('a + 3')).toBe(4);
|
||||
expect(await evalFormual('b * 3')).toBe(6);
|
||||
expect(await evalFormual('b * 3 + 4')).toBe(10);
|
||||
expect(await evalFormual('c * (3 + 4)')).toBe(21);
|
||||
expect(await evalFormual('d / (a + 1)')).toBe(2);
|
||||
expect(await evalFormual('5 % 3')).toBe(2);
|
||||
expect(await evalFormual('3 | 4')).toBe(7);
|
||||
expect(await evalFormual('4 ^ 4')).toBe(0);
|
||||
expect(await evalFormual('4 ^ 4')).toBe(0);
|
||||
expect(await evalFormual('4 & 4')).toBe(4);
|
||||
expect(await evalFormual('4 & 3')).toBe(0);
|
||||
expect(await evalFormual('~-1')).toBe(0);
|
||||
expect(await evalFormual('!!1')).toBe(true);
|
||||
expect(await evalFormual('!!""')).toBe(false);
|
||||
expect(await evalFormual('1 || 2')).toBe(1);
|
||||
expect(await evalFormual('1 && 2')).toBe(2);
|
||||
expect(await evalFormual('1 && 2 || 3')).toBe(2);
|
||||
expect(await evalFormual('1 || 2 || 3')).toBe(1);
|
||||
expect(await evalFormual('1 || 2 && 3')).toBe(1);
|
||||
expect(await evalFormual('(1 || 2) && 3')).toBe(3);
|
||||
expect(await evalFormual('1 == "1"')).toBe(true);
|
||||
expect(await evalFormual('1 === "1"')).toBe(false);
|
||||
expect(await evalFormual('1 < 1')).toBe(false);
|
||||
expect(await evalFormual('1 <= 1')).toBe(true);
|
||||
expect(await evalFormual('1 > 1')).toBe(false);
|
||||
expect(await evalFormual('1 >= 1')).toBe(true);
|
||||
expect(await evalFormual('3 >> 1')).toBe(1);
|
||||
expect(await evalFormual('3 << 1')).toBe(6);
|
||||
expect(await evalFormual('10 ** 3')).toBe(1000);
|
||||
|
||||
expect(await evalFormual('10 ? 3 : 2')).toBe(3);
|
||||
expect(await evalFormual('0 ? 3 : 2')).toBe(2);
|
||||
});
|
||||
|
||||
test('formula:expression2', async () => {
|
||||
expect(await evalFormual('a[0]', {a: [1, 2, 3]})).toBe(1);
|
||||
expect(await evalFormual('a[b]', {a: [1, 2, 3], b: 1})).toBe(2);
|
||||
expect(await evalFormual('a[b - 1]', {a: [1, 2, 3], b: 1})).toBe(1);
|
||||
expect(await evalFormual('a[b ? 1 : 2]', {a: [1, 2, 3], b: 1})).toBe(2);
|
||||
expect(await evalFormual('a[c ? 1 : 2]', {a: [1, 2, 3], b: 1})).toBe(3);
|
||||
});
|
||||
|
||||
test('formula:expression3', async () => {
|
||||
// expect(await evalFormual('${a} === "b"', {a: 'b'})).toBe(true);
|
||||
expect(await evalFormual('b === "b"')).toBe(false);
|
||||
// expect(await evalFormual('${a}', {a: 'b'})).toBe('b');
|
||||
|
||||
expect(await evalFormual('obj.x.a', {obj: {x: {a: 1}}})).toBe(1);
|
||||
expect(await evalFormual('obj.y.a', {obj: {x: {a: 1}}})).toBe(undefined);
|
||||
});
|
||||
|
||||
test('formula:if', async () => {
|
||||
expect(await evalFormual('IF(true, 2, 3)')).toBe(2);
|
||||
expect(await evalFormual('IF(false, 2, 3)')).toBe(3);
|
||||
expect(await evalFormual('IF(false, 2, IF(true, 3, 4))')).toBe(3);
|
||||
});
|
||||
|
||||
test('formula:and', async () => {
|
||||
expect(!!(await evalFormual('AND(0, 1)'))).toBe(false);
|
||||
expect(!!(await evalFormual('AND(1, 1)'))).toBe(true);
|
||||
expect(!!(await evalFormual('AND(1, 1, 1, 0)'))).toBe(false);
|
||||
});
|
||||
|
||||
test('formula:or', async () => {
|
||||
expect(!!(await evalFormual('OR(0, 1)'))).toBe(true);
|
||||
expect(!!(await evalFormual('OR(1, 1)'))).toBe(true);
|
||||
expect(!!(await evalFormual('OR(1, 1, 1, 0)'))).toBe(true);
|
||||
expect(!!(await evalFormual('OR(0, 0, 0, 0)'))).toBe(false);
|
||||
});
|
||||
|
||||
test('formula:xor', async () => {
|
||||
expect(await evalFormual('XOR(0, 1)')).toBe(true);
|
||||
expect(await evalFormual('XOR(1, 0)')).toBe(true);
|
||||
expect(await evalFormual('XOR(1, 1)')).toBe(false);
|
||||
expect(await evalFormual('XOR(0, 0)')).toBe(false);
|
||||
|
||||
expect(await evalFormual('XOR(0, 0, 1)')).toBe(true);
|
||||
expect(await evalFormual('XOR(0, 1, 1)')).toBe(false);
|
||||
});
|
||||
|
||||
test('formula:ifs', async () => {
|
||||
expect(!!(await evalFormual('IFS(0, 1, 2)'))).toBe(true);
|
||||
expect(!!(await evalFormual('IFS(0, 1, 2, 2, 3)'))).toBe(true);
|
||||
expect(!!(await evalFormual('IFS(0, 1, 0, 2, 0)'))).toBe(false);
|
||||
expect(await evalFormual('IFS(0, 1, 2, 2)')).toBe(2);
|
||||
expect(await evalFormual('IFS(0, 1, 0, 2)')).toBe(undefined);
|
||||
});
|
||||
|
||||
test('formula:math', async () => {
|
||||
expect(await evalFormual('ABS(1)')).toBe(1);
|
||||
expect(await evalFormual('ABS(-1)')).toBe(1);
|
||||
expect(await evalFormual('ABS(0)')).toBe(0);
|
||||
|
||||
expect(await evalFormual('MAX(1, -1, 2, 3, 5, -9)')).toBe(5);
|
||||
expect(await evalFormual('MIN(1, -1, 2, 3, 5, -9)')).toBe(-9);
|
||||
|
||||
expect(await evalFormual('MOD(3, 2)')).toBe(1);
|
||||
|
||||
expect(await evalFormual('PI()')).toBe(Math.PI);
|
||||
|
||||
expect(await evalFormual('ROUND(3.55)')).toBe(3.55);
|
||||
expect(await evalFormual('ROUND(3.45)')).toBe(3.45);
|
||||
|
||||
expect(await evalFormual('ROUND(3.456789, 2)')).toBe(3.46);
|
||||
expect(await evalFormual('CEIL(3.456789)')).toBe(3.46);
|
||||
expect(await evalFormual('FLOOR(3.456789)')).toBe(3.45);
|
||||
|
||||
expect(await evalFormual('SQRT(4)')).toBe(2);
|
||||
expect(await evalFormual('AVG(4, 6, 10, 10, 10)')).toBe(8);
|
||||
|
||||
// 示例来自 https://support.microsoft.com/zh-cn/office/devsq-%E5%87%BD%E6%95%B0-8b739616-8376-4df5-8bd0-cfe0a6caf444
|
||||
expect(await evalFormual('DEVSQ(4,5,8,7,11,4,3)')).toBe(48);
|
||||
// 示例来自 https://support.microsoft.com/zh-cn/office/avedev-%E5%87%BD%E6%95%B0-58fe8d65-2a84-4dc7-8052-f3f87b5c6639
|
||||
expect(await evalFormual('ROUND(AVEDEV(4,5,6,7,5,4,3), 2)')).toBe(1.02);
|
||||
// 示例来自 https://support.microsoft.com/zh-cn/office/harmean-%E5%87%BD%E6%95%B0-5efd9184-fab5-42f9-b1d3-57883a1d3bc6
|
||||
expect(await evalFormual('ROUND(HARMEAN(4,5,8,7,11,4,3), 3)')).toBe(5.028);
|
||||
|
||||
expect(await evalFormual('LARGE([1,3,5,4,7,6], 3)')).toBe(5);
|
||||
expect(await evalFormual('LARGE([1,3,5,4,7,6], 1)')).toBe(7);
|
||||
|
||||
expect(await evalFormual('UPPERMONEY(7682.01)')).toBe('柒仟陆佰捌拾贰元壹分');
|
||||
expect(await evalFormual('UPPERMONEY(7682)')).toBe('柒仟陆佰捌拾贰元整');
|
||||
|
||||
// 非数字类型转换是否正常?
|
||||
expect(await evalFormual('"3" + "3"')).toBe(6);
|
||||
expect(await evalFormual('"3" - "3"')).toBe(0);
|
||||
expect(await evalFormual('AVG(4, "6", "10", 10, 10)')).toBe(8);
|
||||
expect(await evalFormual('MAX(4, "6", "10", 2, 3)')).toBe(10);
|
||||
|
||||
expect(await evalFormual('"a" + "b"')).toBe('ab');
|
||||
});
|
||||
|
||||
test('formula:text', async () => {
|
||||
expect(await evalFormual('LEFT("abcdefg", 2)')).toBe('ab');
|
||||
expect(await evalFormual('RIGHT("abcdefg", 2)')).toBe('fg');
|
||||
expect(await evalFormual('LENGTH("abcdefg")')).toBe(7);
|
||||
expect(await evalFormual('LEN("abcdefg")')).toBe(7);
|
||||
expect(await evalFormual('ISEMPTY("abcdefg")')).toBe(false);
|
||||
expect(await evalFormual('ISEMPTY("")')).toBe(true);
|
||||
expect(await evalFormual('CONCATENATE("a", "b", "c", "d")')).toBe('abcd');
|
||||
expect(await evalFormual('CHAR(97)')).toBe('a');
|
||||
expect(await evalFormual('LOWER("AB")')).toBe('ab');
|
||||
expect(await evalFormual('UPPER("ab")')).toBe('AB');
|
||||
expect(await evalFormual('SPLIT("a,b,c")')).toMatchObject(['a', 'b', 'c']);
|
||||
expect(await evalFormual('TRIM(" ab ")')).toBe('ab');
|
||||
expect(await evalFormual('STARTSWITH("xab", "ab")')).toBe(false);
|
||||
expect(await evalFormual('STARTSWITH("xab", "x")')).toBe(true);
|
||||
expect(await evalFormual('ENDSWITH("xab", "x")')).toBe(false);
|
||||
expect(await evalFormual('ENDSWITH("xab", "b")')).toBe(true);
|
||||
expect(await evalFormual('UPPERFIRST("xab")')).toBe('Xab');
|
||||
expect(await evalFormual('PADSTART("5", 3, "0")')).toBe('005');
|
||||
expect(await evalFormual('PADSTART(5, 3, 0)')).toBe('005');
|
||||
expect(await evalFormual('CAPITALIZE("star")')).toBe('Star');
|
||||
expect(await evalFormual('ESCAPE("&")')).toBe('&');
|
||||
expect(await evalFormual('TRUNCATE("amis.baidu.com", 7)')).toBe('amis...');
|
||||
expect(await evalFormual('BEFORELAST("amis.baidu.com", ".")')).toBe(
|
||||
'amis.baidu'
|
||||
);
|
||||
expect(await evalFormual('BEFORELAST("amis", ".")')).toBe('amis');
|
||||
expect(await evalFormual('STRIPTAG("<b>amis</b>")')).toBe('amis');
|
||||
expect(await evalFormual('LINEBREAK("am\nis")')).toBe('am<br/>is');
|
||||
expect(await evalFormual('CONTAINS("xab", "x")')).toBe(true);
|
||||
expect(await evalFormual('CONTAINS("xab", "b")')).toBe(true);
|
||||
expect(await evalFormual('REPLACE("xabab", "ab", "cd")')).toBe('xcdcd');
|
||||
expect(await evalFormual('SEARCH("xabab", "ab")')).toBe(1);
|
||||
expect(await evalFormual('SEARCH("xabab", "cd")')).toBe(-1);
|
||||
expect(await evalFormual('SEARCH("xabab", "ab", 2)')).toBe(3);
|
||||
expect(await evalFormual('MID("xabab", 2, 2)')).toBe('ba');
|
||||
});
|
||||
|
||||
test('formula:date', async () => {
|
||||
expect(await evalFormual('TIMESTAMP(DATE(2021, 11, 21, 0, 0, 0), "x")')).toBe(
|
||||
new Date(2021, 11, 21, 0, 0, 0).getTime()
|
||||
);
|
||||
expect(
|
||||
await evalFormual('DATETOSTR(DATE(2021, 11, 21, 0, 0, 0), "YYYY-MM-DD")')
|
||||
).toBe('2021-12-21');
|
||||
expect(await evalFormual('DATETOSTR(DATE("2021-12-21"), "YYYY-MM-DD")')).toBe(
|
||||
'2021-12-21'
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR(TODAY(), "YYYY-MM-DD")')).toBe(
|
||||
moment().format('YYYY-MM-DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR(NOW(), "YYYY-MM-DD")')).toBe(
|
||||
moment().format('YYYY-MM-DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR(1676563200, "YYYY-MM-DD")')).toBe(
|
||||
moment(1676563200, 'X').format('YYYY-MM-DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR(1676563200000, "YYYY-MM-DD")')).toBe(
|
||||
moment(1676563200000, 'x').format('YYYY-MM-DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR("12/25/2022", "YYYY-MM-DD")')).toBe(
|
||||
moment('12/25/2022').format('YYYY-MM-DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR("12-25-2022", "YYYY/MM/DD")')).toBe(
|
||||
moment('12-25-2022').format('YYYY/MM/DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR("2022年12月25日", "YYYY/MM/DD")')).toBe(
|
||||
moment('2022年12月25日', 'YYYY-MM-DD').format('YYYY/MM/DD')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATETOSTR("2022年12月25日 14时23分56秒", "YYYY/MM/DD HH:mm:ss")'
|
||||
)
|
||||
).toBe(
|
||||
moment('2022年12月25日 14时23分56秒', 'YYYY-MM-DD HH:mm:ss').format(
|
||||
'YYYY/MM/DD HH:mm:ss'
|
||||
)
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR("20230105", "YYYY/MM/DD")')).toBe(
|
||||
moment('20230105', 'YYYY-MM-DD').format('YYYY/MM/DD')
|
||||
);
|
||||
expect(await evalFormual('DATETOSTR("2023.01.05", "YYYY/MM/DD")')).toBe(
|
||||
moment('2023.01.05', 'YYYY-MM-DD').format('YYYY/MM/DD')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATETOSTR("2010-10-20 4:30 +0000", "YYYY-MM-DD HH:mm Z")'
|
||||
)
|
||||
).toBe(moment('2010-10-20 4:30 +0000').format('YYYY-MM-DD HH:mm Z'));
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATETOSTR("2013-02-04T10:35:24-08:00", "YYYY-MM-DD HH:mm:ss")'
|
||||
)
|
||||
).toBe(moment('2013-02-04T10:35:24-08:00').format('YYYY-MM-DD HH:mm:ss'));
|
||||
expect(await evalFormual('YEAR(STRTODATE("2021-10-24 10:10:10"))')).toBe(
|
||||
2021
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATERANGESPLIT("1676563200,1676735999", undefined, "YYYY.MM.DD hh:mm:ss")'
|
||||
)
|
||||
).toEqual(['2023.02.17 12:00:00', '2023.02.18 11:59:59']);
|
||||
expect(await evalFormual('DATERANGESPLIT("1676563200,1676735999", 0)')).toBe(
|
||||
'1676563200'
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATERANGESPLIT("1676563200,1676735999", 0 , "YYYY.MM.DD hh:mm:ss")'
|
||||
)
|
||||
).toBe('2023.02.17 12:00:00');
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATERANGESPLIT("1676563200,1676735999", "start" , "YYYY.MM.DD hh:mm:ss")'
|
||||
)
|
||||
).toBe('2023.02.17 12:00:00');
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATERANGESPLIT("1676563200,1676735999", 1 , "YYYY.MM.DD hh:mm:ss")'
|
||||
)
|
||||
).toBe('2023.02.18 11:59:59');
|
||||
expect(
|
||||
await evalFormual(
|
||||
'DATERANGESPLIT("1676563200,1676735999", "end" , "YYYY.MM.DD hh:mm:ss")'
|
||||
)
|
||||
).toBe('2023.02.18 11:59:59');
|
||||
expect(await evalFormual('WEEKDAY("2023-02-27")')).toBe(
|
||||
moment('2023-02-27').weekday()
|
||||
);
|
||||
expect(await evalFormual('WEEKDAY("2023-02-27", 2)')).toBe(
|
||||
moment('2023-02-27').isoWeekday()
|
||||
);
|
||||
expect(await evalFormual('WEEK("2023-03-05")')).toBe(
|
||||
moment('2023-03-05').week()
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-03-08", ["2023-03-01", "2024-04-07"], "year")'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-03-08').isBetween('2023-03-01', '2024-04-07', 'year', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2022-03-08", ["2023-03-01", "2024-04-07"], "year")'
|
||||
)
|
||||
).toBe(
|
||||
moment('2022-03-08').isBetween('2023-03-01', '2024-04-07', 'year', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-03-08", ["2023-03-01", "2023-04-07"], "month")'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-03-08').isBetween('2023-03-01', '2023-04-07', 'month', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-05-08", ["2023-03-01", "2023-04-07", "month"])'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-05-08').isBetween('2023-03-01', '2023-04-07', 'month', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-03-06", ["2023-03-01", "2023-05-07"])'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-03-06').isBetween('2023-03-01', '2023-05-07', 'day', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-05-08", ["2023-03-01", "2023-05-07"])'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-05-08').isBetween('2023-03-01', '2023-05-07', 'day', '[]')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'BETWEENRANGE("2023-05-07", ["2023-03-01", "2023-05-07"], "day", "()")'
|
||||
)
|
||||
).toBe(
|
||||
moment('2023-05-07').isBetween('2023-03-01', '2023-05-07', 'day', '()')
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'CONCATENATE(STARTOF("2023-02-28", "day"), "," ,ENDOF("2023-02-28", "day"))'
|
||||
)
|
||||
).toBe(
|
||||
`${moment('2023-02-28').startOf('day').toDate()},${moment('2023-02-28')
|
||||
.endOf('day')
|
||||
.toDate()}`
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'CONCATENATE(STARTOF("2023-02-28", "day", "YYYY-MM-DD HH:mm:ss"), ",", ENDOF("2023-02-28", "day", "YYYY-MM-DD HH:mm:ss"))'
|
||||
)
|
||||
).toBe(
|
||||
`${moment('2023-02-28')
|
||||
.startOf('day')
|
||||
.format('YYYY-MM-DD HH:mm:ss')},${moment('2023-02-28')
|
||||
.endOf('day')
|
||||
.format('YYYY-MM-DD HH:mm:ss')}`
|
||||
);
|
||||
expect(
|
||||
await evalFormual(
|
||||
'CONCATENATE(STARTOF("2023-02-28", "day", "X"), "," ,ENDOF("2023-02-28", "day", "X"))'
|
||||
)
|
||||
).toBe(
|
||||
`${moment('2023-02-28').startOf('day').format('X')},${moment('2023-02-28')
|
||||
.endOf('day')
|
||||
.format('X')}`
|
||||
);
|
||||
});
|
||||
|
||||
test('formula:last', async () => {
|
||||
expect(await evalFormual('LAST([1, 2, 3])')).toBe(3);
|
||||
});
|
||||
|
||||
test('formula:basename', async () => {
|
||||
expect(await evalFormual('BASENAME("/home/amis/a.json")')).toBe('a.json');
|
||||
});
|
||||
|
||||
test('formula:customFunction', async () => {
|
||||
registerFunction('ASYNCFUNCTION', async input => {
|
||||
const response = await Promise.resolve({
|
||||
status: 0,
|
||||
data: {
|
||||
name: 'amis'
|
||||
}
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
expect(await evalFormual('ASYNCFUNCTION()')).toMatchObject({
|
||||
status: 0,
|
||||
data: {
|
||||
name: 'amis'
|
||||
}
|
||||
});
|
||||
});
|
@ -2265,7 +2265,7 @@ export class Evaluator {
|
||||
}
|
||||
}
|
||||
|
||||
function getCookie(name: string) {
|
||||
export function getCookie(name: string) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) {
|
||||
@ -2274,7 +2274,7 @@ function getCookie(name: string) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseJson(str: string, defaultValue?: any) {
|
||||
export function parseJson(str: string, defaultValue?: any) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
@ -2282,7 +2282,7 @@ function parseJson(str: string, defaultValue?: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function stripNumber(number: number) {
|
||||
export function stripNumber(number: number) {
|
||||
if (typeof number === 'number' && !Number.isInteger(number)) {
|
||||
return parseFloat(number.toPrecision(16));
|
||||
} else {
|
||||
@ -2292,7 +2292,7 @@ function stripNumber(number: number) {
|
||||
|
||||
// 如果只有一个成员,同时第一个成员为 args
|
||||
// 则把它展开,当成是多个参数,毕竟公式里面还不支持 ...args 语法,
|
||||
function normalizeArgs(args: Array<any>) {
|
||||
export function normalizeArgs(args: Array<any>) {
|
||||
if (args.length === 1 && Array.isArray(args[0])) {
|
||||
args = args[0];
|
||||
}
|
||||
|
659
packages/amis-formula/src/evalutorForAsync.ts
Normal file
659
packages/amis-formula/src/evalutorForAsync.ts
Normal file
@ -0,0 +1,659 @@
|
||||
/**
|
||||
* @file 公式内置函数
|
||||
*/
|
||||
import {FilterContext} from './types';
|
||||
import {createObject, Evaluator, stripNumber} from './evalutor';
|
||||
|
||||
export async function runSequence<T, U>(
|
||||
arr: Array<T>,
|
||||
fn: (item: T, index: number) => Promise<U>
|
||||
) {
|
||||
const result: Array<U> = [];
|
||||
|
||||
await arr.reduce(async (promise, item: T, index: number) => {
|
||||
await promise;
|
||||
result.push(await fn(item, index));
|
||||
}, Promise.resolve());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export class AsyncEvaluator extends (Evaluator as any) {
|
||||
constructor(data: any, options?: any) {
|
||||
super(data, options);
|
||||
}
|
||||
|
||||
async document(ast: {type: 'document'; body: Array<any>}) {
|
||||
if (!ast.body.length) {
|
||||
return undefined;
|
||||
}
|
||||
const isString = ast.body.length > 1;
|
||||
const content = await runSequence(ast.body, async item => {
|
||||
let result = this.evalute(item);
|
||||
|
||||
if (isString && result == null) {
|
||||
// 不要出现 undefined, null 之类的文案
|
||||
return '';
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return content.length === 1 ? content[0] : content.join('');
|
||||
}
|
||||
|
||||
async filter(ast: {
|
||||
type: 'filter';
|
||||
input: any;
|
||||
filters: Array<{name: string; args: Array<any>}>;
|
||||
}) {
|
||||
let input = await this.evalute(ast.input);
|
||||
const filters = ast.filters.concat();
|
||||
const context: FilterContext = {
|
||||
filter: undefined,
|
||||
data: this.context,
|
||||
restFilters: filters
|
||||
};
|
||||
|
||||
const result = await filters.reduce(async (ps, filter, index) => {
|
||||
const input = await ps;
|
||||
const fn = this.filters[filter.name];
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`filter \`${filter.name}\` not exists.`);
|
||||
}
|
||||
context.filter = filter;
|
||||
|
||||
const argsRes = await filter.args.reduce(async (promise, item) => {
|
||||
await promise;
|
||||
if (item?.type === 'mixed') {
|
||||
return runSequence(item.body, item =>
|
||||
typeof item === 'string' ? item : this.evalute(item)
|
||||
);
|
||||
} else if (item.type) {
|
||||
return this.evalute(item);
|
||||
}
|
||||
return item;
|
||||
}, Promise.resolve([]));
|
||||
|
||||
return fn.apply(context, [input].concat(argsRes));
|
||||
}, Promise.resolve(input));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async template(ast: {type: 'template'; body: Array<any>}) {
|
||||
const args = await runSequence(ast.body, arg => this.evalute(arg));
|
||||
return args.join('');
|
||||
}
|
||||
|
||||
// 下标获取
|
||||
async getter(ast: {host: any; key: any}) {
|
||||
const host = await this.evalute(ast.host);
|
||||
let key = await this.evalute(ast.key);
|
||||
if (typeof key === 'undefined' && ast.key?.type === 'variable') {
|
||||
key = ast.key.name;
|
||||
}
|
||||
return host?.[key];
|
||||
}
|
||||
|
||||
// 位操作如 +2 ~3 !
|
||||
async unary(ast: {op: '+' | '-' | '~' | '!'; value: any}) {
|
||||
let value = await this.evalute(ast.value);
|
||||
|
||||
switch (ast.op) {
|
||||
case '+':
|
||||
return +value;
|
||||
case '-':
|
||||
return -value;
|
||||
case '~':
|
||||
return ~value;
|
||||
case '!':
|
||||
return !value;
|
||||
}
|
||||
}
|
||||
|
||||
async power(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
return Math.pow(this.formatNumber(left), this.formatNumber(right));
|
||||
}
|
||||
|
||||
async multiply(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
return stripNumber(this.formatNumber(left) * this.formatNumber(right));
|
||||
}
|
||||
|
||||
async divide(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
return stripNumber(this.formatNumber(left) / this.formatNumber(right));
|
||||
}
|
||||
|
||||
async remainder(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
return this.formatNumber(left) % this.formatNumber(right);
|
||||
}
|
||||
|
||||
async add(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
// 如果有一个不是数字就变成字符串拼接
|
||||
if (isNaN(left) || isNaN(right)) {
|
||||
return left + right;
|
||||
}
|
||||
return stripNumber(this.formatNumber(left) + this.formatNumber(right));
|
||||
}
|
||||
|
||||
async minus(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
return stripNumber(this.formatNumber(left) - this.formatNumber(right));
|
||||
}
|
||||
|
||||
async shift(ast: {op: '<<' | '>>' | '>>>'; left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.formatNumber(this.evalute(ast.right), true);
|
||||
|
||||
if (ast.op === '<<') {
|
||||
return left << right;
|
||||
} else if (ast.op == '>>') {
|
||||
return left >> right;
|
||||
} else {
|
||||
return left >>> right;
|
||||
}
|
||||
}
|
||||
|
||||
async lt(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left < right;
|
||||
}
|
||||
|
||||
async gt(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
return left > right;
|
||||
}
|
||||
|
||||
async le(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left <= right;
|
||||
}
|
||||
|
||||
async ge(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left >= right;
|
||||
}
|
||||
|
||||
async eq(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left == right;
|
||||
}
|
||||
|
||||
async ne(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left != right;
|
||||
}
|
||||
|
||||
async streq(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left === right;
|
||||
}
|
||||
|
||||
async strneq(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
// todo 如果是日期的对比,这个地方可以优化一下。
|
||||
|
||||
return left !== right;
|
||||
}
|
||||
|
||||
async binary(ast: {op: '&' | '^' | '|'; left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
const right = await this.evalute(ast.right);
|
||||
|
||||
if (ast.op === '&') {
|
||||
return left & right;
|
||||
} else if (ast.op === '^') {
|
||||
return left ^ right;
|
||||
} else {
|
||||
return left | right;
|
||||
}
|
||||
}
|
||||
|
||||
async and(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
return left && this.evalute(ast.right);
|
||||
}
|
||||
|
||||
async or(ast: {left: any; right: any}) {
|
||||
const left = await this.evalute(ast.left);
|
||||
return left || this.evalute(ast.right);
|
||||
}
|
||||
|
||||
array(ast: {type: 'array'; members: Array<any>}) {
|
||||
return runSequence(ast.members, member => this.evalute(member));
|
||||
}
|
||||
|
||||
async object(ast: {members: Array<{key: string; value: any}>}) {
|
||||
let object: any = {};
|
||||
await ast.members.reduce(
|
||||
async (promise: any, {key, value}: any, index: number) => {
|
||||
await promise;
|
||||
const objKey = await this.evalute(key);
|
||||
const objVal = await this.evalute(value);
|
||||
object[objKey] = objVal;
|
||||
},
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
async conditional(ast: {
|
||||
type: 'conditional';
|
||||
test: any;
|
||||
consequent: any;
|
||||
alternate: any;
|
||||
}) {
|
||||
return (await this.evalute(ast.test))
|
||||
? await this.evalute(ast.consequent)
|
||||
: await this.evalute(ast.alternate);
|
||||
}
|
||||
|
||||
async funcCall(this: any, ast: {identifier: string; args: Array<any>}) {
|
||||
const fnName = `fn${ast.identifier}`;
|
||||
const fn =
|
||||
this.functions[fnName] ||
|
||||
this[fnName] ||
|
||||
(this.filters.hasOwnProperty(ast.identifier) &&
|
||||
this.filters[ast.identifier]);
|
||||
|
||||
if (!fn) {
|
||||
throw new Error(`${ast.identifier}函数没有定义`);
|
||||
}
|
||||
|
||||
let args: Array<any> = ast.args;
|
||||
|
||||
// 逻辑函数特殊处理,因为有时候有些运算是可以跳过的。
|
||||
if (~['IF', 'AND', 'OR', 'XOR', 'IFS'].indexOf(ast.identifier)) {
|
||||
args = args.map(a => () => this.evalute(a));
|
||||
} else {
|
||||
args = await runSequence(args, a => this.evalute(a));
|
||||
}
|
||||
|
||||
return fn.apply(this, args);
|
||||
}
|
||||
|
||||
async callAnonymousFunction(
|
||||
ast: {
|
||||
args: any[];
|
||||
return: any;
|
||||
},
|
||||
args: Array<any>
|
||||
) {
|
||||
const ctx: any = createObject(
|
||||
this.contextStack[this.contextStack.length - 1]('&') || {},
|
||||
{}
|
||||
);
|
||||
ast.args.forEach((arg: any) => {
|
||||
if (arg.type !== 'variable') {
|
||||
throw new Error('expected a variable as argument');
|
||||
}
|
||||
ctx[arg.name] = args.shift();
|
||||
});
|
||||
this.contextStack.push((varName: string) =>
|
||||
varName === '&' ? ctx : ctx[varName]
|
||||
);
|
||||
const result = await this.evalute(ast.return);
|
||||
this.contextStack.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:IF(A, B, C)
|
||||
*
|
||||
* 如果满足条件A,则返回B,否则返回C,支持多层嵌套IF函数。
|
||||
*
|
||||
* 也可以用表达式如:A ? B : C
|
||||
*
|
||||
* @example IF(condition, consequent, alternate)
|
||||
* @param {expression} condition - 条件表达式.
|
||||
* @param {any} consequent 条件判断通过的返回结果
|
||||
* @param {any} alternate 条件判断不通过的返回结果
|
||||
* @namespace 逻辑函数
|
||||
*
|
||||
* @returns {any} 根据条件返回不同的结果
|
||||
*/
|
||||
async fnIF(
|
||||
condition: () => any,
|
||||
trueValue: () => any,
|
||||
falseValue: () => any
|
||||
) {
|
||||
return (await condition()) ? await trueValue() : await falseValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件全部符合,返回 true,否则返回 false
|
||||
*
|
||||
* 示例:AND(语文成绩>80, 数学成绩>80)
|
||||
*
|
||||
* 语文成绩和数学成绩都大于 80,则返回 true,否则返回 false
|
||||
*
|
||||
* 也可以直接用表达式如:语文成绩>80 && 数学成绩>80
|
||||
*
|
||||
* @example AND(expression1, expression2, ...expressionN)
|
||||
* @param {...expression} conditions - 条件表达式.
|
||||
* @namespace 逻辑函数
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async fnAND(...condtions: Array<() => any>) {
|
||||
if (!condtions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return condtions.reduce(async (promise, c) => {
|
||||
const result = await promise;
|
||||
if (result) {
|
||||
return c();
|
||||
}
|
||||
return result;
|
||||
}, Promise.resolve(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件任意一个满足条件,返回 true,否则返回 false
|
||||
*
|
||||
* 示例:OR(语文成绩>80, 数学成绩>80)
|
||||
*
|
||||
* 语文成绩和数学成绩任意一个大于 80,则返回 true,否则返回 false
|
||||
*
|
||||
* 也可以直接用表达式如:语文成绩>80 || 数学成绩>80
|
||||
*
|
||||
* @example OR(expression1, expression2, ...expressionN)
|
||||
* @param {...expression} conditions - 条件表达式.
|
||||
* @namespace 逻辑函数
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async fnOR(...condtions: Array<() => any>) {
|
||||
if (!condtions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return condtions.reduce(async (promise, c) => {
|
||||
const result = await promise;
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
return c();
|
||||
}, Promise.resolve(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 异或处理,多个表达式组中存在奇数个真时认为真。
|
||||
*
|
||||
* @example XOR(condition1, condition2)
|
||||
* @param {expression} condition1 - 条件表达式1
|
||||
* @param {expression} condition2 - 条件表达式2
|
||||
* @namespace 逻辑函数
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async fnXOR(...condtions: Array<() => any>) {
|
||||
if (!condtions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!(
|
||||
(await runSequence(condtions, c => c())).filter(item => item).length % 2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断函数集合,相当于多个 else if 合并成一个。
|
||||
*
|
||||
* 示例:IFS(语文成绩 > 80, "优秀", 语文成绩 > 60, "良", "继续努力")
|
||||
*
|
||||
* 如果语文成绩大于 80,则返回优秀,否则判断大于 60 分,则返回良,否则返回继续努力。
|
||||
*
|
||||
* @example IFS(condition1, result1, condition2, result2,...conditionN, resultN)
|
||||
* @param {...any} args - 条件,返回值集合
|
||||
* @namespace 逻辑函数
|
||||
* @returns {any} 第一个满足条件的结果,没有命中的返回 false。
|
||||
*/
|
||||
async fnIFS(...args: Array<() => any>) {
|
||||
if (args.length % 2) {
|
||||
args.splice(args.length - 1, 0, () => true);
|
||||
}
|
||||
|
||||
while (args.length) {
|
||||
const c = args.shift()!;
|
||||
const v = args.shift()!;
|
||||
|
||||
if (await c()) {
|
||||
return await v();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组做数据转换,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYMAP(arr, item => item)
|
||||
* @returns {boolean} 结果
|
||||
*/
|
||||
fnARRAYMAP(value: any, iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
return (Array.isArray(value) ? value : []).reduce(
|
||||
async (promise, item, index) => {
|
||||
const arr = await promise;
|
||||
arr.push(await this.callAnonymousFunction(iterator, [item, index]));
|
||||
return arr;
|
||||
},
|
||||
Promise.resolve([])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据做数据过滤,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
* 将第二个箭头函数返回为 false 的成员过滤掉。
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYFILTER(arr, item => item)
|
||||
* @returns {boolean} 结果
|
||||
*/
|
||||
async fnARRAYFILTER(value: any, iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
return await (Array.isArray(value) ? value : []).reduce(
|
||||
async (promise, item, index) => {
|
||||
let arr = await promise;
|
||||
const hit = await this.callAnonymousFunction(iterator, [item, index]);
|
||||
|
||||
if (hit) {
|
||||
arr.push(item);
|
||||
}
|
||||
|
||||
return arr;
|
||||
},
|
||||
Promise.resolve([])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据做数据查找,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
* 找出第二个箭头函数返回为 true 的成员的索引。
|
||||
*
|
||||
* 示例:
|
||||
*
|
||||
* ARRAYFINDINDEX([0, 2, false], item => item === 2) 得到 1
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYFINDINDEX(arr, item => item === 2)
|
||||
* @returns {number} 结果
|
||||
*/
|
||||
async fnARRAYFINDINDEX(arr: any[], iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
let hitIndex = -1;
|
||||
|
||||
await (Array.isArray(arr) ? arr : []).reduce(
|
||||
async (promise: any, item: any, index: number) => {
|
||||
await promise;
|
||||
const hit = await this.callAnonymousFunction(iterator, [item, index]);
|
||||
|
||||
if (hit) {
|
||||
hitIndex = index;
|
||||
}
|
||||
},
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
return hitIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据做数据查找,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
* 找出第二个箭头函数返回为 true 的成员。
|
||||
*
|
||||
* 示例:
|
||||
*
|
||||
* ARRAYFIND([0, 2, false], item => item === 2) 得到 2
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYFIND(arr, item => item === 2)
|
||||
* @returns {any} 结果
|
||||
*/
|
||||
async fnARRAYFIND(arr: any[], iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
let hitItem = undefined;
|
||||
|
||||
await (Array.isArray(arr) ? arr : []).reduce(
|
||||
async (promise: any, item: any, index: number) => {
|
||||
await promise;
|
||||
const hit = await this.callAnonymousFunction(iterator, [item, index]);
|
||||
|
||||
if (hit) {
|
||||
hitItem = item;
|
||||
}
|
||||
},
|
||||
Promise.resolve()
|
||||
);
|
||||
|
||||
return hitItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据做数据遍历判断,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
* 判断第二个箭头函数是否存在返回为 true 的成员。
|
||||
*
|
||||
* 示例:
|
||||
*
|
||||
* ARRAYSOME([0, 2, false], item => item === 2) 得到 true
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYSOME(arr, item => item === 2)
|
||||
* @returns {boolean} 结果
|
||||
*/
|
||||
async fnARRAYSOME(arr: any[], iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
let result = await (Array.isArray(arr) ? arr : []).reduce(
|
||||
async (promise: any, item: any, index: number) => {
|
||||
const prev = await promise;
|
||||
const hit = await this.callAnonymousFunction(iterator, [item, index]);
|
||||
return prev || hit;
|
||||
},
|
||||
Promise.resolve(false)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据做数据遍历判断,需要搭配箭头函数一起使用,注意箭头函数只支持单表达式用法。
|
||||
* 判断第二个箭头函数返回是否都为 true。
|
||||
*
|
||||
* 示例:
|
||||
*
|
||||
* ARRAYEVERY([0, 2, false], item => item === 2) 得到 false
|
||||
*
|
||||
* @param {Array<any>} arr 数组
|
||||
* @param {Function<any>} iterator 箭头函数
|
||||
* @namespace 数组
|
||||
* @example ARRAYEVERY(arr, item => item === 2)
|
||||
* @returns {boolean} 结果
|
||||
*/
|
||||
async fnARRAYEVERY(arr: any[], iterator: any) {
|
||||
if (!iterator || iterator.type !== 'anonymous_function') {
|
||||
throw new Error('expected an anonymous function get ' + iterator);
|
||||
}
|
||||
|
||||
let result = await (Array.isArray(arr) ? arr : []).reduce(
|
||||
async (promise: any, item: any, index: number) => {
|
||||
const prev = await promise;
|
||||
const hit = await this.callAnonymousFunction(iterator, [item, index]);
|
||||
|
||||
return prev && hit;
|
||||
},
|
||||
Promise.resolve(true)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import {Evaluator} from './evalutor';
|
||||
import {AsyncEvaluator} from './evalutorForAsync';
|
||||
import {parse} from './parser';
|
||||
import {lexer} from './lexer';
|
||||
import {registerFilter, filters, getFilters, extendsFilters} from './filter';
|
||||
@ -13,6 +14,7 @@ export {
|
||||
parse,
|
||||
lexer,
|
||||
Evaluator,
|
||||
AsyncEvaluator,
|
||||
FilterContext,
|
||||
filters,
|
||||
getFilters,
|
||||
@ -34,4 +36,18 @@ export function evaluate(
|
||||
return new Evaluator(data, options).evalute(ast);
|
||||
}
|
||||
|
||||
export async function evaluateForAsync(
|
||||
astOrString: string | ASTNode,
|
||||
data: any,
|
||||
options?: ParserOptions & EvaluatorOptions
|
||||
) {
|
||||
let ast: ASTNode = astOrString as ASTNode;
|
||||
if (typeof astOrString === 'string') {
|
||||
ast = parse(astOrString, options);
|
||||
}
|
||||
|
||||
return new AsyncEvaluator(data, options).evalute(ast);
|
||||
}
|
||||
|
||||
Evaluator.setDefaultFilters(getFilters());
|
||||
AsyncEvaluator.setDefaultFilters(getFilters());
|
||||
|
@ -1,13 +1,8 @@
|
||||
import {
|
||||
ExpressionComplex,
|
||||
ConditionBuilderField,
|
||||
ConditionBuilderFuncs,
|
||||
ConditionFieldFunc,
|
||||
ExpressionFunc,
|
||||
ConditionBuilderType,
|
||||
FieldSimple,
|
||||
FieldGroup,
|
||||
OperatorType
|
||||
FieldSimple
|
||||
} from './types';
|
||||
import React from 'react';
|
||||
import ConditionField from './Field';
|
||||
@ -27,6 +22,7 @@ import ConditionFunc from './Func';
|
||||
import {ConditionBuilderConfig} from './config';
|
||||
import Formula from './Formula';
|
||||
import {FormulaPickerProps} from '../formula/Picker';
|
||||
import type {ExpressionComplex, OperatorType, ExpressionFunc} from 'amis-core';
|
||||
|
||||
/**
|
||||
* 支持4中表达式设置方式
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ConditionFieldFunc,
|
||||
ExpressionFunc,
|
||||
ConditionBuilderField,
|
||||
ConditionBuilderFuncs
|
||||
} from './types';
|
||||
@ -21,6 +20,7 @@ import ResultBox from '../ResultBox';
|
||||
import {Icon} from '../icons';
|
||||
import Expression from './Expression';
|
||||
import {ConditionBuilderConfig} from './config';
|
||||
import type {ExpressionFunc} from 'amis-core';
|
||||
|
||||
export interface ConditionFuncProps extends ThemeProps, LocaleProps {
|
||||
value: ExpressionFunc;
|
||||
|
@ -1,9 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ConditionBuilderFields,
|
||||
ConditionGroupValue,
|
||||
ConditionBuilderFuncs
|
||||
} from './types';
|
||||
import {ConditionBuilderFields, ConditionBuilderFuncs} from './types';
|
||||
import {
|
||||
ThemeProps,
|
||||
themeable,
|
||||
@ -11,7 +7,8 @@ import {
|
||||
utils,
|
||||
localeable,
|
||||
LocaleProps,
|
||||
guid
|
||||
guid,
|
||||
ConditionGroupValue
|
||||
} from 'amis-core';
|
||||
import Button from '../Button';
|
||||
import GroupOrItem from './GroupOrItem';
|
||||
|
@ -1,10 +1,5 @@
|
||||
import {ConditionBuilderConfig} from './config';
|
||||
import {
|
||||
ConditionBuilderFields,
|
||||
ConditionGroupValue,
|
||||
ConditionBuilderFuncs,
|
||||
ConditionValue
|
||||
} from './types';
|
||||
import {ConditionBuilderFields, ConditionBuilderFuncs} from './types';
|
||||
import {ThemeProps, themeable, autobind} from 'amis-core';
|
||||
import React from 'react';
|
||||
import {Icon} from '../icons';
|
||||
@ -12,6 +7,7 @@ import ConditionGroup from './Group';
|
||||
import ConditionItem from './Item';
|
||||
import {FormulaPickerProps} from '../formula/Picker';
|
||||
import Button from '../Button';
|
||||
import type {ConditionGroupValue, ConditionValue} from 'amis-core';
|
||||
|
||||
export interface CBGroupOrItemProps extends ThemeProps {
|
||||
builderMode?: 'simple' | 'full';
|
||||
|
@ -2,15 +2,10 @@ import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {
|
||||
ConditionBuilderFields,
|
||||
ConditionRule,
|
||||
ConditionBuilderFuncs,
|
||||
ExpressionFunc,
|
||||
ConditionFieldFunc,
|
||||
ConditionBuilderField,
|
||||
FieldSimple,
|
||||
ExpressionField,
|
||||
OperatorType,
|
||||
ExpressionComplex
|
||||
FieldSimple
|
||||
} from './types';
|
||||
import {
|
||||
ThemeProps,
|
||||
@ -30,7 +25,14 @@ import GroupedSelection from '../GroupedSelection';
|
||||
import ResultBox from '../ResultBox';
|
||||
|
||||
import {FormulaPickerProps} from '../formula/Picker';
|
||||
import type {PlainObject} from 'amis-core';
|
||||
import type {
|
||||
PlainObject,
|
||||
ConditionRule,
|
||||
OperatorType,
|
||||
ExpressionFunc,
|
||||
ExpressionField,
|
||||
ExpressionComplex
|
||||
} from 'amis-core';
|
||||
|
||||
const option2value = (item: any) => item.value;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {FieldSimple, OperatorType} from './types';
|
||||
import {FieldSimple} from './types';
|
||||
import {ThemeProps, themeable, localeable, LocaleProps} from 'amis-core';
|
||||
import InputBox from '../InputBox';
|
||||
import NumberInput from '../NumberInput';
|
||||
@ -7,6 +7,7 @@ import DatePicker from '../DatePicker';
|
||||
import {SelectWithRemoteOptions as Select} from '../Select';
|
||||
import Switch from '../Switch';
|
||||
import {FormulaPicker, FormulaPickerProps} from '../formula/Picker';
|
||||
import type {OperatorType} from 'amis-core';
|
||||
|
||||
export interface ValueProps extends ThemeProps, LocaleProps {
|
||||
value: any;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
FieldTypes,
|
||||
OperatorType,
|
||||
ConditionBuilderFuncs,
|
||||
ConditionBuilderFields,
|
||||
ConditionBuilderType
|
||||
} from './types';
|
||||
import type {OperatorType} from 'amis-core';
|
||||
|
||||
export interface BaseFieldConfig {
|
||||
operations: Array<OperatorType>;
|
||||
|
@ -14,16 +14,13 @@ import {
|
||||
noop
|
||||
} from 'amis-core';
|
||||
import {uncontrollable} from 'amis-core';
|
||||
import {
|
||||
ConditionBuilderFields,
|
||||
ConditionGroupValue,
|
||||
ConditionBuilderFuncs
|
||||
} from './types';
|
||||
import {ConditionBuilderFields, ConditionBuilderFuncs} from './types';
|
||||
import ConditionGroup from './Group';
|
||||
import defaultConfig, {ConditionBuilderConfig} from './config';
|
||||
import {FormulaPickerProps} from '../formula/Picker';
|
||||
import PickerContainer from '../PickerContainer';
|
||||
import ResultBox from '../ResultBox';
|
||||
import type {ConditionGroupValue} from 'amis-core';
|
||||
|
||||
export interface ConditionBuilderProps extends ThemeProps, LocaleProps {
|
||||
builderMode?: 'simple' | 'full'; // 简单模式|完整模式
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Api, BaseApiObject} from 'amis-core';
|
||||
import type {BaseApiObject, OperatorType} from 'amis-core';
|
||||
|
||||
export type FieldTypes =
|
||||
| 'text'
|
||||
@ -10,78 +10,11 @@ export type FieldTypes =
|
||||
| 'select'
|
||||
| 'custom';
|
||||
|
||||
export type OperatorType =
|
||||
| 'equal'
|
||||
| 'not_equal'
|
||||
| 'is_empty'
|
||||
| 'is_not_empty'
|
||||
| 'like'
|
||||
| 'not_like'
|
||||
| 'starts_with'
|
||||
| 'ends_with'
|
||||
| 'less'
|
||||
| 'less_or_equal'
|
||||
| 'greater'
|
||||
| 'greater_or_equal'
|
||||
| 'between'
|
||||
| 'not_between'
|
||||
| 'select_equals'
|
||||
| 'select_not_equals'
|
||||
| 'select_any_in'
|
||||
| 'select_not_any_in'
|
||||
| {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type FieldItem = {
|
||||
type: 'text';
|
||||
operators: Array<OperatorType>;
|
||||
};
|
||||
|
||||
export type ExpressionSimple = string | number | object | undefined;
|
||||
export type ExpressionValue =
|
||||
| ExpressionSimple
|
||||
| {
|
||||
type: 'value';
|
||||
value: ExpressionSimple;
|
||||
};
|
||||
export type ExpressionFunc = {
|
||||
type: 'func';
|
||||
func: string;
|
||||
args: Array<ExpressionComplex>;
|
||||
};
|
||||
export type ExpressionField = {
|
||||
type: 'field';
|
||||
field: string;
|
||||
};
|
||||
export type ExpressionFormula = {
|
||||
type: 'formula';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ExpressionComplex =
|
||||
| ExpressionValue
|
||||
| ExpressionFunc
|
||||
| ExpressionField
|
||||
| ExpressionFormula;
|
||||
|
||||
export interface ConditionRule {
|
||||
id: any;
|
||||
left?: ExpressionComplex;
|
||||
op?: OperatorType;
|
||||
right?: ExpressionComplex | Array<ExpressionComplex>;
|
||||
}
|
||||
|
||||
export interface ConditionGroupValue {
|
||||
id: string;
|
||||
conjunction: 'and' | 'or';
|
||||
not?: boolean;
|
||||
children?: Array<ConditionRule | ConditionGroupValue>;
|
||||
}
|
||||
|
||||
export interface ConditionValue extends ConditionGroupValue {}
|
||||
|
||||
interface customOperator {
|
||||
lable: string;
|
||||
value: string;
|
||||
|
Loading…
Reference in New Issue
Block a user