feat(plugin-workflow): add all crud nodes for workflow (#293)

This commit is contained in:
Junyi 2022-04-16 23:49:50 +08:00 committed by GitHub
parent 28da6a5f82
commit 10dedd87e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 309 additions and 254 deletions

View File

@ -1,4 +1,4 @@
export * from './useCollection'; export * from './useCollection';
export * from './useCollectionField'; export * from './useCollectionField';
export * from './useCollectionManager'; export * from './useCollectionManager';
export * from './useCollectionDataSource';

View File

@ -0,0 +1,17 @@
import { action } from '@formily/reactive';
import { useCollectionManager } from ".";
import { useCompile } from "../../schema-component";
export function useCollectionDataSource() {
return (field: any) => {
const compile = useCompile();
const { collections = [] } = useCollectionManager();
action.bound((data: any) => {
field.dataSource = data.map(item => ({
label: compile(item.title),
value: item.name
}));
})(collections);
}
}

View File

@ -16,7 +16,7 @@ const useDef = (options) => {
export const Filter: any = observer((props: any) => { export const Filter: any = observer((props: any) => {
const { useDataSource = useDef } = props; const { useDataSource = useDef } = props;
const { options, dynamicComponent } = useProps(props); const { options, dynamicComponent, className } = useProps(props);
const field = useField<ObjectFieldModel>(); const field = useField<ObjectFieldModel>();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
useDataSource({ useDataSource({
@ -25,7 +25,7 @@ export const Filter: any = observer((props: any) => {
}, },
}); });
return ( return (
<div> <div className={className}>
<FilterContext.Provider <FilterContext.Provider
value={{ field, fieldSchema, dynamicComponent, options: options || field.dataSource || [] }} value={{ field, fieldSchema, dynamicComponent, options: options || field.dataSource || [] }}
> >

View File

@ -97,7 +97,7 @@ export const InputRecordPicker: React.FC<any> = (props) => {
open={false} open={false}
/> />
<RecordPickerContext.Provider value={{ multiple, onChange, selectedRows, setSelectedRows }}> <RecordPickerContext.Provider value={{ multiple, onChange, selectedRows, setSelectedRows }}>
<CollectionProvider name={collectionField.target}> <CollectionProvider name={collectionField?.target}>
<ActionContext.Provider value={{ openMode: 'drawer', visible, setVisible }}> <ActionContext.Provider value={{ openMode: 'drawer', visible, setVisible }}>
<FormProvider> <FormProvider>
<SchemaComponentOptions scope={{ useTableSelectorProps, usePickActionProps }}> <SchemaComponentOptions scope={{ useTableSelectorProps, usePickActionProps }}>

View File

@ -1,10 +1,13 @@
import React from "react"; import React from "react";
import { observer } from "@formily/react";
import { FormItem } from "@formily/antd";
import { Cascader, DatePicker, Input, InputNumber, Select } from "antd"; import { Cascader, DatePicker, Input, InputNumber, Select } from "antd";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { instructions, useNodeContext } from "./nodes"; import { instructions, useNodeContext } from "./nodes";
import { useFlowContext } from "./WorkflowCanvas"; import { useFlowContext } from "./WorkflowCanvas";
import { triggers } from "./triggers"; import { triggers } from "./triggers";
import { SchemaComponent, useCompile } from "..";
function NullRender() { function NullRender() {
return null; return null;
@ -325,3 +328,101 @@ export function Calculation({ calculator, operands = [], onChange }) {
</VariableTypesContext.Provider> </VariableTypesContext.Provider>
); );
} }
export function VariableComponent({ value, onChange, renderSchemaComponent }) {
const VTypes = { ...VariableTypes,
constant: {
title: '常量',
value: 'constant',
options: undefined
}
};
const operand = typeof value === 'string'
? parseStringValue(value, VTypes)
: { type: 'constant', value };
return (
<VariableTypesContext.Provider value={VTypes}>
<Operand
value={operand}
onChange={(next) => {
if (next.type !== operand.type && next.type === 'constant') {
onChange(null);
} else {
const { stringify } = VTypes[next.type];
onChange(stringify(next));
}
}}
>
{operand.type === 'constant' ? renderSchemaComponent() : null}
</Operand>
</VariableTypesContext.Provider>
);
}
// NOTE: observer for watching useProps
export const CollectionFieldset = observer(({ value, onChange, useProps }: any) => {
const { fields } = useProps();
const compile = useCompile();
const VTypes = { ...VariableTypes,
constant: {
title: '常量',
value: 'constant',
options: undefined
}
};
return (
<fieldset className={css`
margin-top: .5em;
.ant-formily-item{
.ant-formily-item-label{
line-height: 32px;
}
.ant-select,
.ant-cascader-picker,
.ant-picker,
.ant-input-number,
.ant-input-affix-wrapper{
width: auto;
}
}
`}>
{fields.length
? fields.map(field => {
const operand = typeof value[field.name] === 'string'
? parseStringValue(value[field.name], VTypes)
: { type: 'constant', value: value[field.name] };
return (
<FormItem label={compile(field.title)}>
<VariableTypesContext.Provider value={VTypes}>
<Operand
value={operand}
onChange={(next) => {
if (next.type !== operand.type && next.type === 'constant') {
onChange({ ...value, [field.name]: null });
} else {
const { stringify } = VTypes[next.type];
onChange({ ...value, [field.name]: stringify(next) });
}
}}
>
{operand.type === 'constant'
? <SchemaComponent schema={field.schema} />
: null
}
</Operand>
</VariableTypesContext.Provider>
</FormItem>
);
})
: <p></p>
}
</fieldset>
);
});

View File

@ -1,30 +1,17 @@
import React from 'react'; import React from 'react';
import { observer, useForm } from '@formily/react';
import { action } from '@formily/reactive';
import { Select } from 'antd'; import { Select } from 'antd';
import { t } from 'i18next';
import { css } from '@emotion/css';
import { SchemaComponent, useCollectionManager } from '../..'; import { useCollectionDataSource, useCollectionManager, useCompile } from '../..';
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
import { useFlowContext } from '../WorkflowCanvas'; import { useFlowContext } from '../WorkflowCanvas';
import { Operand, parseStringValue, VariableTypes, VariableTypesContext, BaseTypeSet } from '../calculators'; import { collection, values } from '../schemas/collection';
import { FormItem } from '@formily/antd'; import { BaseTypeSet, CollectionFieldset } from '../calculators';
export default { export default {
title: '新增', title: '新增数据',
type: 'create', type: 'create',
group: 'model', group: 'model',
fieldset: { fieldset: {
collection: { collection,
type: 'string',
title: '数据表',
name: 'collection',
required: true,
'x-reactions': ['{{useCollectionDataSource()}}'],
'x-decorator': 'FormItem',
'x-component': 'Select',
},
// multiple: { // multiple: {
// type: 'boolean', // type: 'boolean',
// title: '多条数据', // title: '多条数据',
@ -38,21 +25,10 @@ export default {
params: { params: {
type: 'object', type: 'object',
name: 'params', name: 'params',
title: '数据', title: '',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
properties: { properties: {
values: { values
type: 'object',
title: '',
name: 'values',
'x-component': 'DynamicFieldset',
'x-component-props': {
useProps() {
const { values } = useForm();
return { fields: useCollectionFilterOptions(values.collection) };
}
}
}
} }
} }
}, },
@ -60,82 +36,13 @@ export default {
}, },
scope: { scope: {
useCollectionDataSource() { useCollectionDataSource
return (field: any) => {
const { collections = [] } = useCollectionManager();
action.bound((data: any) => {
field.dataSource = data.map(item => ({
label: t(item.title),
value: item.name
}));
})(collections);
}
}
}, },
components: { components: {
// NOTE: observer for watching useProps CollectionFieldset
DynamicFieldset: observer(({ value, onChange, useProps }: any) => {
const { fields } = useProps();
const VTypes = { ...VariableTypes,
constant: {
title: '常量',
value: 'constant',
options: undefined
}
};
return (
<fieldset className={css`
margin-top: .5em;
.ant-formily-item{
.ant-formily-item-label{
line-height: 32px;
}
.ant-select,
.ant-cascader-picker,
.ant-picker,
.ant-input-number,
.ant-input-affix-wrapper{
width: auto;
}
}
`}>
{fields.map(field => {
const operand = typeof value[field.name] === 'string'
? parseStringValue(value[field.name], VTypes)
: { type: 'constant', value: value[field.name] };
return (
<FormItem label={field.title}>
<VariableTypesContext.Provider value={VTypes}>
<Operand
value={operand}
onChange={(next) => {
if (next.type !== operand.type && next.type === 'constant') {
onChange({ ...value, [field.name]: null });
} else {
const { stringify } = VTypes[next.type];
onChange({ ...value, [field.name]: stringify(next) });
}
}}
>
{operand.type === 'constant'
? <SchemaComponent schema={field.schema} />
: null
}
</Operand>
</VariableTypesContext.Provider>
</FormItem>
);
})}
</fieldset>
);
})
}, },
getter({ type, options, onChange }) { getter({ type, options, onChange }) {
const compile = useCompile();
const { collections = [] } = useCollectionManager(); const { collections = [] } = useCollectionManager();
const { nodes } = useFlowContext(); const { nodes } = useFlowContext();
const { config } = nodes.find(n => n.id == options.nodeId); const { config } = nodes.find(n => n.id == options.nodeId);
@ -148,7 +55,7 @@ export default {
{collection.fields {collection.fields
.filter(field => BaseTypeSet.has(field.uiSchema.type)) .filter(field => BaseTypeSet.has(field.uiSchema.type))
.map(field => ( .map(field => (
<Select.Option key={field.name} value={field.name}>{t(field.uiSchema.title)}</Select.Option> <Select.Option key={field.name} value={field.name}>{compile(field.uiSchema.title)}</Select.Option>
))} ))}
</Select> </Select>
); );

View File

@ -0,0 +1,31 @@
import { useCollectionDataSource } from '../..';
import { VariableComponent } from '../calculators';
import { collection, filter } from '../schemas/collection';
export default {
title: '删除数据',
type: 'destroy',
group: 'model',
fieldset: {
collection,
params: {
type: 'object',
name: 'params',
title: '',
'x-decorator': 'FormItem',
properties: {
filter
}
}
},
view: {
},
scope: {
useCollectionDataSource
},
components: {
VariableComponent
}
};

View File

@ -13,11 +13,38 @@ import { nodeClass, nodeCardClass, nodeHeaderClass, nodeTitleClass, nodeBlockCla
import query from './query'; import query from './query';
import create from './create'; import create from './create';
import update from './update';
import destroy from './destroy';
import condition from './condition'; import condition from './condition';
import parallel from './parallel'; import parallel from './parallel';
import calculation from './calculation'; import calculation from './calculation';
export interface Instruction {
title: string;
type: string;
group: string;
options?: { label: string; value: any; key: string }[];
fieldset: { [key: string]: ISchema };
view?: ISchema;
scope?: { [key: string]: any };
components?: { [key: string]: any };
render?(props): React.ReactElement;
endding?: boolean;
getter?(node: any): React.ReactElement;
};
export const instructions = new Registry<Instruction>();
instructions.register('query', query);
instructions.register('create', create);
instructions.register('update', update);
instructions.register('destroy', destroy);
instructions.register('condition', condition);
instructions.register('parallel', parallel);
instructions.register('calculation', calculation);
function useUpdateConfigAction() { function useUpdateConfigAction() {
const form = useForm(); const form = useForm();
const api = useAPIClient(); const api = useAPIClient();
@ -41,30 +68,6 @@ function useUpdateConfigAction() {
}; };
}; };
export interface Instruction {
title: string;
type: string;
group: string;
options?: { label: string; value: any; key: string }[];
fieldset: { [key: string]: ISchema };
view?: ISchema;
scope?: { [key: string]: any };
components?: { [key: string]: any };
render?(props): React.ReactElement;
endding?: boolean;
getter?(node: any): React.ReactElement;
};
export const instructions = new Registry<Instruction>();
instructions.register('query', query);
instructions.register('create', create);
instructions.register('condition', condition);
instructions.register('parallel', parallel);
instructions.register('calculation', calculation);
const NodeContext = React.createContext(null); const NodeContext = React.createContext(null);
export function useNodeContext() { export function useNodeContext() {

View File

@ -1,29 +1,18 @@
import React from 'react'; import React from 'react';
import { useForm } from '@formily/react';
import { action } from '@formily/reactive';
import { Select } from 'antd'; import { Select } from 'antd';
import { t } from 'i18next';
import { css } from '@emotion/css';
import { useCollectionManager } from '../..'; import { useCollectionDataSource, useCollectionManager, useCompile } from '../..';
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
import { useFlowContext } from '../WorkflowCanvas'; import { useFlowContext } from '../WorkflowCanvas';
import { Operand, parseStringValue, VariableTypes, VariableTypesContext, BaseTypeSet } from '../calculators'; import { BaseTypeSet, VariableComponent } from '../calculators';
import { collection, filter } from '../schemas/collection';
export default { export default {
title: '查询', title: '查询数据',
type: 'query', type: 'query',
group: 'model', group: 'model',
fieldset: { fieldset: {
collection: { collection,
type: 'string',
title: '数据表',
name: 'collection',
required: true,
'x-reactions': ['{{useCollectionDataSource()}}'],
'x-decorator': 'FormItem',
'x-component': 'Select',
},
multiple: { multiple: {
type: 'boolean', type: 'boolean',
title: '多条数据', title: '多条数据',
@ -37,31 +26,10 @@ export default {
params: { params: {
type: 'object', type: 'object',
name: 'params', name: 'params',
title: '查询参数', title: '',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
properties: { properties: {
filter: { filter
type: 'object',
title: '筛选条件',
name: 'filter',
'x-decorator': 'div',
'x-decorator-props': {
className: css`
.ant-select{
width: auto;
}
`
},
'x-component': 'Filter',
'x-component-props': {
useProps() {
const { values } = useForm();
const options = useCollectionFilterOptions(values.collection);
return { options };
},
dynamicComponent: 'VariableComponent'
}
}
} }
} }
}, },
@ -69,52 +37,13 @@ export default {
}, },
scope: { scope: {
useCollectionDataSource() { useCollectionDataSource
return (field: any) => {
const { collections = [] } = useCollectionManager();
action.bound((data: any) => {
field.dataSource = data.map(item => ({
label: t(item.title),
value: item.name
}));
})(collections);
}
}
}, },
components: { components: {
VariableComponent({ value, onChange, renderSchemaComponent }) { VariableComponent
const VTypes = { ...VariableTypes,
constant: {
title: '常量',
value: 'constant',
options: undefined
}
};
const operand = typeof value === 'string'
? parseStringValue(value, VTypes)
: { type: 'constant', value };
return (
<VariableTypesContext.Provider value={VTypes}>
<Operand
value={operand}
onChange={(next) => {
if (next.type !== operand.type && next.type === 'constant') {
onChange(null);
} else {
const { stringify } = VTypes[next.type];
onChange(stringify(next));
}
}}
>
{operand.type === 'constant' ? renderSchemaComponent() : null}
</Operand>
</VariableTypesContext.Provider>
);
}
}, },
getter({ type, options, onChange }) { getter({ type, options, onChange }) {
const compile = useCompile();
const { collections = [] } = useCollectionManager(); const { collections = [] } = useCollectionManager();
const { nodes } = useFlowContext(); const { nodes } = useFlowContext();
const { config } = nodes.find(n => n.id == options.nodeId); const { config } = nodes.find(n => n.id == options.nodeId);
@ -127,7 +56,7 @@ export default {
{collection.fields {collection.fields
.filter(field => BaseTypeSet.has(field.uiSchema.type)) .filter(field => BaseTypeSet.has(field.uiSchema.type))
.map(field => ( .map(field => (
<Select.Option key={field.name} value={field.name}>{t(field.uiSchema.title)}</Select.Option> <Select.Option key={field.name} value={field.name}>{compile(field.uiSchema.title)}</Select.Option>
))} ))}
</Select> </Select>
); );

View File

@ -0,0 +1,32 @@
import { useCollectionDataSource } from '../..';
import { CollectionFieldset, VariableComponent } from '../calculators';
import { collection, filter, values } from '../schemas/collection';
export default {
title: '更新数据',
type: 'update',
group: 'model',
fieldset: {
collection,
params: {
type: 'object',
name: 'params',
title: '',
'x-decorator': 'FormItem',
properties: {
filter,
values
}
}
},
view: {
},
scope: {
useCollectionDataSource
},
components: {
VariableComponent,
CollectionFieldset
}
};

View File

@ -0,0 +1,64 @@
import { css } from "@emotion/css";
import { useForm } from "@formily/react";
import { useCollectionFilterOptions } from "../../collection-manager/action-hooks";
export const collection = {
type: 'string',
title: '数据表',
name: 'collection',
required: true,
'x-reactions': ['{{useCollectionDataSource()}}'],
'x-decorator': 'FormItem',
'x-component': 'Select',
};
export const values = {
type: 'object',
title: '数据内容',
name: 'values',
'x-decorator': 'FormItem',
'x-decorator-props': {
labelAlign: 'left',
className: css`
flex-direction: column;
`
},
'x-component': 'CollectionFieldset',
'x-component-props': {
useProps() {
const { values: form } = useForm();
const fields = useCollectionFilterOptions(form.collection);
return { fields };
}
}
};
export const filter = {
type: 'object',
title: '筛选条件',
name: 'filter',
'x-decorator': 'FormItem',
'x-decorator-props': {
labelAlign: 'left',
className: css`
flex-direction: column;
`
},
'x-component': 'Filter',
'x-component-props': {
useProps() {
const { values } = useForm();
const options = useCollectionFilterOptions(values.collection);
return {
options,
className: css`
position: relative;
width: 100%;
padding: .5em 1em;
border: 1px dashed #ddd;
`
};
},
dynamicComponent: 'VariableComponent'
}
};

View File

@ -1,27 +1,18 @@
import React from 'react'; import React from 'react';
import { action } from '@formily/reactive';
import { t } from 'i18next';
import { Select } from 'antd'; import { Select } from 'antd';
import { useCollectionManager } from '../../collection-manager'; import { useCollectionDataSource, useCollectionManager } from '../../collection-manager';
import { useCompile } from '../../schema-component';
import { useFlowContext } from '../WorkflowCanvas'; import { useFlowContext } from '../WorkflowCanvas';
import { BaseTypeSet } from '../calculators'; import { BaseTypeSet } from '../calculators';
import { useForm } from '@formily/react'; import { collection, filter } from '../schemas/collection';
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
export default { export default {
title: '数据表事件', title: '数据表事件',
type: 'model', type: 'model',
fieldset: { fieldset: {
collection: { collection,
type: 'string',
title: '数据表',
name: 'collection',
required: true,
'x-reactions': ['{{useAsyncDataSource()}}'],
'x-decorator': 'FormItem',
'x-component': 'Select',
},
mode: { mode: {
type: 'number', type: 'number',
title: '触发时机', title: '触发时机',
@ -38,35 +29,15 @@ export default {
} }
}, },
filter: { filter: {
type: 'object', ...filter,
title: '满足条件', title: '满足条件'
description: 'not supported by server yet',
name: 'filter',
'x-decorator': 'FormItem',
'x-component': 'Filter',
'x-component-props': {
useProps() {
const { values } = useForm();
const options = useCollectionFilterOptions(values.collection);
return { options };
}
}
} }
}, },
scope: { scope: {
useAsyncDataSource() { useCollectionDataSource
return (field: any) => {
const { collections = [] } = useCollectionManager();
action.bound((data: any) => {
field.dataSource = data.map(item => ({
label: t(item.title),
value: item.name
}));
})(collections);
}
}
}, },
getter({ type, options, onChange }) { getter({ type, options, onChange }) {
const compile = useCompile();
const { collections = [] } = useCollectionManager(); const { collections = [] } = useCollectionManager();
const { workflow } = useFlowContext(); const { workflow } = useFlowContext();
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] }; const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
@ -82,7 +53,7 @@ export default {
{collection.fields {collection.fields
.filter(field => BaseTypeSet.has(field?.uiSchema?.type)) .filter(field => BaseTypeSet.has(field?.uiSchema?.type))
.map(field => ( .map(field => (
<Select.Option key={field.name} value={field.name}>{t(field.uiSchema.title)}</Select.Option> <Select.Option key={field.name} value={field.name}>{compile(field.uiSchema.title)}</Select.Option>
))} ))}
</Select> </Select>
); );