From c4afb7586c043ce37e4887b06abe2bd9c0814e9c Mon Sep 17 00:00:00 2001 From: Junyi Date: Sun, 10 Apr 2022 20:06:40 +0800 Subject: [PATCH] Feat/plugin workflow (#278) * fix(plugin-workflow): fix query node variable config * feat(plugin-workflow): add more calculators * refactor(plugin-workflow): simplify operand codes and fix variable component bugs --- packages/client/src/workflow/calculators.tsx | 140 ++++++++++++------ .../client/src/workflow/nodes/calculation.tsx | 31 ++++ packages/client/src/workflow/nodes/index.tsx | 2 + packages/client/src/workflow/nodes/query.tsx | 69 +++------ .../plugin-workflow/src/calculators/index.ts | 35 +++++ 5 files changed, 187 insertions(+), 90 deletions(-) create mode 100644 packages/client/src/workflow/nodes/calculation.tsx diff --git a/packages/client/src/workflow/calculators.tsx b/packages/client/src/workflow/calculators.tsx index 45ebee120..8239eceec 100644 --- a/packages/client/src/workflow/calculators.tsx +++ b/packages/client/src/workflow/calculators.tsx @@ -10,8 +10,46 @@ function NullRender() { } export const calculators = [ - { value: 'equal', name: '等于' }, - { value: 'notEqual', name: '不等于' } + { + value: 'boolean', + title: '值比较', + children: [ + { value: 'equal', name: '=' }, + { value: 'notEqual', name: '≠' }, + { value: 'gt', name: '>' }, + { value: 'gte', name: '≥' }, + { value: 'lt', name: '<' }, + { value: 'lte', name: '≤' } + ] + }, + { + value: 'number', + title: '算术运算', + children: [ + { value: 'add', name: '+' }, + { value: 'minus', name: '-' }, + { value: 'multipe', name: '*' }, + { value: 'divide', name: '/' }, + { value: 'mod', name: '%' }, + ] + }, + { + value: 'string', + title: '字符串', + children: [ + { value: 'includes', name: '包含' }, + { value: 'notIncludes', name: '不包含' }, + { value: 'startsWith', name: '开头是' }, + { value: 'notStartsWith', name: '开头不是' }, + { value: 'endsWith', name: '结尾是' }, + { value: 'notEndsWith', name: '结尾不是' } + ] + }, + { + value: 'date', + title: '日期', + children: [] + } ]; const JT_VALUE_RE = /^\s*\{\{([\s\S]*)\}\}\s*$/; @@ -79,17 +117,17 @@ export const VariableTypes = { label: item.title })), component({ options: { type } = { type: 'string' } }) { - return type ? ConstantTypes[type].component : NullRender; + return ConstantTypes[type]?.component ?? NullRender; }, appendTypeValue({ options = { type: 'string' } }) { return options?.type ? [options.type] : []; }, - onTypeChange(props, [type, optionsType], onChange) { + onTypeChange(old, [type, optionsType], onChange) { const { default: value } = ConstantTypes[optionsType]; onChange({ value, type, - options: { ...props.options, type: optionsType } + options: { ...old.options, type: optionsType } }); }, parse(path) { @@ -104,7 +142,7 @@ export const VariableTypes = { const stack = []; for (let current = node.upstream; current; current = current.upstream) { const { getter } = instructions.get(current.type); - // consider `getter` as the key of a value available node + // Note: consider `getter` as the key of a value available node if (getter) { stack.push({ value: current.id, @@ -130,11 +168,11 @@ export const VariableTypes = { appendTypeValue({ options = {} }: { type: string, options: any }) { return options.nodeId ? [Number.parseInt(options.nodeId, 10)] : []; }, - onTypeChange(props, [type, nodeId], onChange) { + onTypeChange(old, [type, nodeId], onChange) { onChange({ - ...props, + // ...old, type, - options: { ...props.options, nodeId } + options: { nodeId } }); }, parse([nodeId, ...path]) { @@ -155,6 +193,12 @@ export const VariableTypes = { // calculation: Calculation }; +export const VariableTypesContext = React.createContext(null); + +export function useVariableTypes() { + return React.useContext(VariableTypesContext); +} + interface OperandProps { value: { type: string; @@ -165,10 +209,12 @@ interface OperandProps { } export function Operand({ onChange, value: operand = { type: 'constant', value: '', options: { type: 'string' } } }: OperandProps) { + const Types = useVariableTypes(); + const { type } = operand; - const { component, appendTypeValue } = VariableTypes[type]; - const VariableComponent = typeof component === 'function' ? component(operand) : component; + const { component, appendTypeValue } = Types[type]; + const VariableComponent = typeof component === 'function' ? component(operand) : NullRender; return (
{ + options={Object.values(Types).map((item: any) => { const children = typeof item.options === 'function' ? item.options() : item.options; return { label: item.title, value: item.value, children, - disabled: children && !children.length + disabled: children && !children.length, + isLeaf: !children }; })} - onChange={(t: Array) => { - const { onTypeChange } = VariableTypes[t[0]]; + onChange={(next: Array) => { + const { onTypeChange } = Types[next[0]]; if (typeof onTypeChange === 'function') { - onTypeChange(operand, t, onChange); + onTypeChange(operand, next, onChange); } else { - if (t[0] !== type) { - onChange({ type: t[0], value: null }); + if (next[0] !== type) { + onChange({ type: next[0], value: null }); } } }} /> - onChange({ ...operand, value: v })} /> + onChange({ ...op })} />
); } -export function Calculation({ calculator, operands, onChange }) { +export function Calculation({ calculator, operands = [], onChange }) { return ( -
+
- onChange({ calculator, operands: [v, operands[1]] }))} /> - {operands[0] - ? ( - <> - - onChange({ calculator, operands: [operands[0], v] }))} /> - - ) - : null - } -
+ .ant-select{ + width: auto; + min-width: 6em; + } + `}> + onChange({ calculator, operands: [v, operands[1]] }))} /> + {operands[0] + ? ( + <> + + onChange({ calculator, operands: [operands[0], v] }))} /> + + ) + : null + } +
+ ); } diff --git a/packages/client/src/workflow/nodes/calculation.tsx b/packages/client/src/workflow/nodes/calculation.tsx new file mode 100644 index 000000000..522db142a --- /dev/null +++ b/packages/client/src/workflow/nodes/calculation.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Calculation } from '../calculators'; + +export default { + title: '运算', + type: 'calculation', + group: 'control', + fieldset: { + calculation: { + type: 'object', + title: '配置计算', + name: 'calculation', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'CalculationConfig', + } + }, + view: { + + }, + components: { + CalculationConfig({ value, onChange }) { + return ( + + ); + } + }, + getter() { + return
计算值
; + } +}; diff --git a/packages/client/src/workflow/nodes/index.tsx b/packages/client/src/workflow/nodes/index.tsx index 3c93aabcf..e55185d6e 100644 --- a/packages/client/src/workflow/nodes/index.tsx +++ b/packages/client/src/workflow/nodes/index.tsx @@ -14,6 +14,7 @@ import { nodeClass, nodeCardClass, nodeHeaderClass, nodeTitleClass, nodeBlockCla import query from './query'; import condition from './condition'; import parallel from './parallel'; +import calculation from './calculation'; function useUpdateConfigAction() { @@ -60,6 +61,7 @@ export const instructions = new Registry(); instructions.register('query', query); instructions.register('condition', condition); instructions.register('parallel', parallel); +instructions.register('calculation', calculation); const NodeContext = React.createContext(null); diff --git a/packages/client/src/workflow/nodes/query.tsx b/packages/client/src/workflow/nodes/query.tsx index e362e7924..7c754c3ef 100644 --- a/packages/client/src/workflow/nodes/query.tsx +++ b/packages/client/src/workflow/nodes/query.tsx @@ -5,10 +5,10 @@ import { Cascader, Select } from 'antd'; import { t } from 'i18next'; import { css } from '@emotion/css'; -import { useRequest, useCollectionManager } from '../..'; +import { useCollectionManager } from '../..'; import { useCollectionFilterOptions } from '../../collection-manager/action-hooks'; import { useFlowContext } from '../WorkflowCanvas'; -import { parseStringValue, VariableTypes } from '../calculators'; +import { Operand, parseStringValue, VariableTypes, VariableTypesContext } from '../calculators'; const BaseTypeSet = new Set(['boolean', 'number', 'string', 'date']); @@ -56,12 +56,10 @@ export default { }, 'x-component': 'Filter', 'x-component-props': { - useDataSource(options) { + useProps() { const { values } = useForm(); - const data = useCollectionFilterOptions(values.collection); - return useRequest(() => Promise.resolve({ - data - }), options) + const options = useCollectionFilterOptions(values.collection); + return { options }; }, dynamicComponent: 'VariableComponent' } @@ -91,7 +89,10 @@ export default { constant: { title: '常量', value: 'constant', - options: undefined + options: undefined, + component() { + return renderSchemaComponent; + } } }; @@ -99,59 +100,33 @@ export default { ? parseStringValue(value, VTypes) : { type: 'constant', value }; - const { component, appendTypeValue } = VTypes[operand.type]; - const [types, setTypes] = useState([operand.type, ...(appendTypeValue ? appendTypeValue(operand) : [])]); - const [type] = types; - - const VariableComponent = typeof component === 'function' ? component(operand) : component; - return ( -
- ({ - label: item.title, - value: item.value, - children: typeof item.options === 'function' ? item.options() : item.options - }))} - onChange={(next: Array) => { - const { onTypeChange, stringify } = VTypes[next[0]]; - setTypes(next); - if (typeof onTypeChange === 'function') { - onTypeChange(operand, next, (op) => { - onChange(stringify(op)); - }); + + { + if (next.type !== operand.type && next.type === 'constant') { + onChange(null); } else { - if (next[0] !== type) { - onChange(null); - } + const { stringify } = VTypes[next.type]; + onChange(stringify(next)); } }} /> - {type === 'constant' - ? renderSchemaComponent() - : { - const { stringify } = VTypes[type]; - onChange(stringify(v)); - }} /> - } -
+ ); } }, - getter({ options, onChange }) { + getter({ type, options, onChange }) { const { collections = [] } = useCollectionManager(); const { nodes } = useFlowContext(); const { config } = nodes.find(n => n.id == options.nodeId); const collection = collections.find(item => item.name === config.collection) ?? { fields: [] }; return ( - { + onChange({ type, options: { ...options, path } }); + }}> {collection.fields .filter(field => BaseTypeSet.has(field.uiSchema.type)) .map(field => ( diff --git a/packages/plugin-workflow/src/calculators/index.ts b/packages/plugin-workflow/src/calculators/index.ts index 0328f076a..468e9e47c 100644 --- a/packages/plugin-workflow/src/calculators/index.ts +++ b/packages/plugin-workflow/src/calculators/index.ts @@ -172,6 +172,41 @@ calculators.register('*', multipe); calculators.register('/', divide); calculators.register('%', mod); +function includes(a, b) { + return a.includes(b); +} + +function notIncludes(a, b) { + return !a.includes(b); +} + +function startsWith(a: string, b: string) { + return a.startsWith(b); +} + +function notStartsWith(a: string, b: string) { + return !a.startsWith(b); +} + +function endsWith(a: string, b: string) { + return a.endsWith(b); +} + +function notEndsWith(a: string, b: string) { + return !a.endsWith(b); +} + +calculators.register('includes', includes); +calculators.register('notIncludes', notIncludes); +calculators.register('startsWith', startsWith); +calculators.register('notStartsWith', notStartsWith); +calculators.register('endsWith', endsWith); +calculators.register('notEndsWith', notEndsWith); + +function before(a: string, b: string) { + return a < b; +} + calculators.register('now', () => new Date()); // TODO: add more common calculators