mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 09:29:16 +00:00
feat(plugin-workflow): add preset and branching config before add node
This commit is contained in:
parent
f019924129
commit
84c62a4ab7
@ -355,6 +355,7 @@ export default class extends Instruction {
|
||||
default: 0,
|
||||
},
|
||||
};
|
||||
branching = true;
|
||||
scope = {
|
||||
renderEngineReference,
|
||||
};
|
||||
|
@ -58,6 +58,7 @@ export default class extends Instruction {
|
||||
default: 'all',
|
||||
},
|
||||
};
|
||||
branching = true;
|
||||
components = {
|
||||
RadioWithTooltip,
|
||||
};
|
||||
@ -118,6 +119,7 @@ export default class extends Instruction {
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setBranchCount(branchCount - 1)}
|
||||
disabled={workflow.executed}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
@ -145,6 +147,7 @@ export default class extends Instruction {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
`}
|
||||
size="small"
|
||||
onClick={() => setBranchCount(branchCount + 1)}
|
||||
disabled={workflow.executed}
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"Parallel branch": "分支",
|
||||
"Parallel branch": "并行分支",
|
||||
"Run multiple branch processes in parallel.": "并行运行多个分支流程。",
|
||||
"Add branch": "增加分支",
|
||||
"Mode": "执行模式",
|
||||
|
@ -8,16 +8,17 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
import { Button, Dropdown, MenuProps, Modal } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
import { css, useAPIClient, useCompile, usePlugin } from '@nocobase/client';
|
||||
import { css, SchemaComponent, useAPIClient, useCompile, usePlugin } from '@nocobase/client';
|
||||
|
||||
import WorkflowPlugin from '.';
|
||||
import { useFlowContext } from './FlowContext';
|
||||
import { NAMESPACE } from './locale';
|
||||
import { Instruction } from './nodes';
|
||||
import useStyles from './style';
|
||||
import { useAddNodeContext } from './AddNodeContext';
|
||||
|
||||
interface AddButtonProps {
|
||||
upstream;
|
||||
@ -29,14 +30,13 @@ export function AddButton(props: AddButtonProps) {
|
||||
const { upstream, branchIndex = null } = props;
|
||||
const engine = usePlugin(WorkflowPlugin);
|
||||
const compile = useCompile();
|
||||
const api = useAPIClient();
|
||||
const { workflow, refresh } = useFlowContext() ?? {};
|
||||
const { workflow } = useFlowContext() ?? {};
|
||||
const instructionList = Array.from(engine.instructions.getValues()) as Instruction[];
|
||||
const { styles } = useStyles();
|
||||
const [creating, setCreating] = useState(false);
|
||||
const { onPreset, onCreate, creating } = useAddNodeContext();
|
||||
|
||||
const groups = useMemo(() => {
|
||||
return [
|
||||
const result = [
|
||||
{ key: 'control', label: `{{t("Control", { ns: "${NAMESPACE}" })}}` },
|
||||
{ key: 'calculation', label: `{{t("Calculation", { ns: "${NAMESPACE}" })}}` },
|
||||
{ key: 'collection', label: `{{t("Collection operations", { ns: "${NAMESPACE}" })}}` },
|
||||
@ -58,62 +58,36 @@ export function AddButton(props: AddButtonProps) {
|
||||
'aria-label': item.type,
|
||||
key: item.type,
|
||||
label: item.title,
|
||||
type: item.options ? 'subMenu' : null,
|
||||
children: item.options
|
||||
? item.options.map((option) => ({
|
||||
role: 'button',
|
||||
'aria-label': option.key,
|
||||
key: option.key,
|
||||
label: option.label,
|
||||
}))
|
||||
: null,
|
||||
})),
|
||||
};
|
||||
})
|
||||
.filter((group) => group.children.length);
|
||||
}, [branchIndex, engine, instructionList, upstream, workflow]);
|
||||
|
||||
const onCreate = useCallback(
|
||||
return compile(result);
|
||||
}, [branchIndex, compile, engine, instructionList, upstream, workflow]);
|
||||
|
||||
const onClick = useCallback(
|
||||
async ({ keyPath }) => {
|
||||
const type = keyPath.pop();
|
||||
const [optionKey] = keyPath;
|
||||
const [type] = keyPath;
|
||||
const instruction = engine.instructions.get(type);
|
||||
const config = instruction.createDefaultConfig();
|
||||
if (optionKey) {
|
||||
const { value } = instruction.options?.find((item) => item.key === optionKey) ?? {};
|
||||
Object.assign(config, typeof value === 'function' ? value() : value);
|
||||
if (!instruction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workflow) {
|
||||
setCreating(true);
|
||||
try {
|
||||
await api.resource('workflows.nodes', workflow.id).create({
|
||||
values: {
|
||||
type,
|
||||
upstreamId: upstream?.id ?? null,
|
||||
branchIndex,
|
||||
title: compile(instruction.title),
|
||||
config,
|
||||
},
|
||||
});
|
||||
refresh();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
const title = compile(instruction.title);
|
||||
const config = instruction.createDefaultConfig?.() ?? {};
|
||||
const data = { instruction, type, title, config, upstream, branchIndex };
|
||||
if (
|
||||
instruction.presetFieldset ||
|
||||
(typeof instruction.branching === 'function' ? instruction.branching(config) : instruction.branching)
|
||||
) {
|
||||
onPreset(data);
|
||||
} else {
|
||||
onCreate(data);
|
||||
}
|
||||
},
|
||||
[api, branchIndex, engine.instructions, refresh, upstream?.id, workflow],
|
||||
[branchIndex, engine.instructions, onCreate, upstream],
|
||||
);
|
||||
|
||||
const menu = useMemo<MenuProps>(() => {
|
||||
return {
|
||||
onClick: onCreate,
|
||||
items: compile(groups),
|
||||
};
|
||||
}, [groups, onCreate]);
|
||||
|
||||
if (!workflow) {
|
||||
return null;
|
||||
}
|
||||
@ -121,8 +95,10 @@ export function AddButton(props: AddButtonProps) {
|
||||
return (
|
||||
<div className={styles.addButtonClass}>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
menu={menu}
|
||||
menu={{
|
||||
items: groups,
|
||||
onClick,
|
||||
}}
|
||||
disabled={workflow.executed}
|
||||
overlayClassName={css`
|
||||
.ant-dropdown-menu-root {
|
||||
@ -135,7 +111,8 @@ export function AddButton(props: AddButtonProps) {
|
||||
aria-label={props['aria-label'] || 'add-button'}
|
||||
shape="circle"
|
||||
icon={<PlusOutlined />}
|
||||
loading={creating}
|
||||
loading={creating?.upstream === upstream && creating?.branchIndex === branchIndex}
|
||||
size="small"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
@ -0,0 +1,249 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useForm } from '@formily/react';
|
||||
|
||||
import {
|
||||
ActionContextProvider,
|
||||
FormItem,
|
||||
SchemaComponent,
|
||||
useActionContext,
|
||||
useAPIClient,
|
||||
useCancelAction,
|
||||
} from '@nocobase/client';
|
||||
|
||||
import { useFlowContext } from './FlowContext';
|
||||
import { lang, NAMESPACE } from './locale';
|
||||
import { RadioWithTooltip } from './components';
|
||||
|
||||
function useAddNodeSubmitAction() {
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
console.log('=======', form.values);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const AddNodeContext = createContext(null);
|
||||
|
||||
export function useAddNodeContext() {
|
||||
return useContext(AddNodeContext);
|
||||
}
|
||||
|
||||
const defaultBranchingOptions = [
|
||||
{
|
||||
label: `{{t('After end of branches', { ns: "${NAMESPACE}" })}}`,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: `{{t('Inside of branch', { ns: "${NAMESPACE}" })}}`,
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const DownstreamBranchIndex = observer((props) => {
|
||||
const { presetting } = useAddNodeContext();
|
||||
const { nodes } = useFlowContext();
|
||||
const { values } = useForm();
|
||||
const options = useMemo(() => {
|
||||
if (!presetting?.instruction) {
|
||||
return [];
|
||||
}
|
||||
const { instruction, upstream, branchIndex } = presetting || {};
|
||||
const downstream = upstream
|
||||
? nodes.find((item) => item.upstreamId === upstream.id && item.branchIndex === branchIndex)
|
||||
: nodes.find((item) => item.upstreamId === null);
|
||||
if (!downstream) {
|
||||
return [];
|
||||
}
|
||||
const branching =
|
||||
typeof instruction.branching === 'function' ? instruction.branching(values.config) : instruction.branching;
|
||||
if (!branching) {
|
||||
return [];
|
||||
}
|
||||
return branching === true ? defaultBranchingOptions : branching;
|
||||
}, [presetting, nodes, values.config]);
|
||||
|
||||
if (!options.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SchemaComponent
|
||||
components={{
|
||||
RadioWithTooltip,
|
||||
}}
|
||||
schema={{
|
||||
name: `${presetting?.type ?? 'unknown'}-${presetting?.upstream?.id ?? 'root'}-${presetting?.branchIndex}`,
|
||||
type: 'void',
|
||||
properties: {
|
||||
downstreamBranchIndex: {
|
||||
type: 'number',
|
||||
title: lang('Move all downstream nodes to', { ns: NAMESPACE }),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'RadioWithTooltip',
|
||||
'x-component-props': {
|
||||
options,
|
||||
direction: 'vertical',
|
||||
},
|
||||
default: false,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <FormItem label={lang('Move all downstream nodes to', { ns: NAMESPACE })}>
|
||||
// <RadioWithTooltip {...props} options={options} defaultValue={-1} direction="vertical" />
|
||||
// </FormItem>
|
||||
// );
|
||||
});
|
||||
|
||||
function PresetFieldset({ useSchema }) {
|
||||
const schema = useSchema();
|
||||
return <SchemaComponent schema={schema} />;
|
||||
}
|
||||
|
||||
export function AddNodeContextProvider(props) {
|
||||
const api = useAPIClient();
|
||||
const [creating, setCreating] = useState(null);
|
||||
const [presetting, setPresetting] = useState(null);
|
||||
const [formValueChanged, setFormValueChanged] = useState(false);
|
||||
const { workflow, refresh } = useFlowContext() ?? {};
|
||||
|
||||
const form = useMemo(() => {
|
||||
return createForm({
|
||||
initialValues: {},
|
||||
values: {},
|
||||
});
|
||||
}, [presetting]);
|
||||
|
||||
const onModalCancel = useCallback(
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
// form.setValues({});
|
||||
// form.clearFormGraph('.config', true);
|
||||
form.reset();
|
||||
setTimeout(() => {
|
||||
setPresetting(null);
|
||||
});
|
||||
}
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const onCreate = useCallback(
|
||||
async ({ type, title, config, upstream, branchIndex }) => {
|
||||
setCreating({ upstream, branchIndex });
|
||||
try {
|
||||
await api.resource('workflows.nodes', workflow.id).create({
|
||||
values: {
|
||||
type,
|
||||
upstreamId: upstream?.id ?? null,
|
||||
branchIndex,
|
||||
title,
|
||||
config,
|
||||
},
|
||||
});
|
||||
refresh();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setCreating(null);
|
||||
}
|
||||
},
|
||||
[api, refresh, workflow.id],
|
||||
);
|
||||
|
||||
const usePresetSchema = useCallback(() => presetting?.instruction.presetFieldset, [presetting]);
|
||||
|
||||
return (
|
||||
<AddNodeContext.Provider value={{ onPreset: setPresetting, presetting, onCreate, creating }}>
|
||||
{props.children}
|
||||
<ActionContextProvider
|
||||
value={{
|
||||
visible: Boolean(presetting),
|
||||
setVisible: onModalCancel,
|
||||
formValueChanged,
|
||||
setFormValueChanged,
|
||||
openSize: 'small',
|
||||
}}
|
||||
>
|
||||
<SchemaComponent
|
||||
components={{
|
||||
DownstreamBranchIndex,
|
||||
}}
|
||||
scope={{
|
||||
useCancelAction,
|
||||
useAddNodeSubmitAction,
|
||||
usePresetSchema,
|
||||
}}
|
||||
schema={{
|
||||
name: `modal`,
|
||||
type: 'void',
|
||||
'x-decorator': 'FormV2',
|
||||
'x-decorator-props': {
|
||||
form,
|
||||
},
|
||||
'x-component': 'Action.Modal',
|
||||
title: `{{ t("Add node", { ns: "${NAMESPACE}" }) }}`,
|
||||
properties: {
|
||||
config: {
|
||||
type: 'void',
|
||||
'x-component': 'PresetFieldset',
|
||||
'x-component-props': {
|
||||
useSchema: '{{ usePresetSchema }}',
|
||||
},
|
||||
// properties: configSchema,
|
||||
},
|
||||
downstreamBranchIndex: {
|
||||
type: 'void',
|
||||
'x-component': 'DownstreamBranchIndex',
|
||||
},
|
||||
footer: {
|
||||
'x-component': 'Action.Modal.Footer',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-component': 'ActionBar',
|
||||
properties: {
|
||||
cancel: {
|
||||
type: 'void',
|
||||
title: '{{ t("Cancel") }}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
useAction: '{{ useCancelAction }}',
|
||||
},
|
||||
},
|
||||
submit: {
|
||||
type: 'void',
|
||||
title: `{{ t("Submit") }}`,
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
useAction: '{{ useAddNodeSubmitAction }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ActionContextProvider>
|
||||
</AddNodeContext.Provider>
|
||||
);
|
||||
}
|
@ -18,6 +18,7 @@ import { useFlowContext } from './FlowContext';
|
||||
import { lang } from './locale';
|
||||
import useStyles from './style';
|
||||
import { TriggerConfig } from './triggers';
|
||||
import { AddNodeContextProvider } from './AddNodeContext';
|
||||
|
||||
export function CanvasContent({ entry }) {
|
||||
const { styles } = useStyles();
|
||||
@ -27,41 +28,43 @@ export function CanvasContent({ entry }) {
|
||||
return (
|
||||
<div className="workflow-canvas-wrapper">
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={console.error}>
|
||||
<div className="workflow-canvas" style={{ zoom: zoom / 100 }}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.branchBlockClass,
|
||||
css`
|
||||
margin-top: 0 !important;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<div className={styles.branchClass}>
|
||||
{workflow?.executed ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
message={lang('Executed workflow cannot be modified. Could be copied to a new version to modify.')}
|
||||
showIcon
|
||||
className={css`
|
||||
margin-bottom: 1em;
|
||||
`}
|
||||
/>
|
||||
) : null}
|
||||
<TriggerConfig />
|
||||
<div
|
||||
className={cx(
|
||||
styles.branchBlockClass,
|
||||
css`
|
||||
margin-top: 0 !important;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<Branch entry={entry} />
|
||||
<AddNodeContextProvider>
|
||||
<div className="workflow-canvas" style={{ zoom: zoom / 100 }}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.branchBlockClass,
|
||||
css`
|
||||
margin-top: 0 !important;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<div className={styles.branchClass}>
|
||||
{workflow?.executed ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
message={lang('Executed workflow cannot be modified. Could be copied to a new version to modify.')}
|
||||
showIcon
|
||||
className={css`
|
||||
margin-bottom: 1em;
|
||||
`}
|
||||
/>
|
||||
) : null}
|
||||
<TriggerConfig />
|
||||
<div
|
||||
className={cx(
|
||||
styles.branchBlockClass,
|
||||
css`
|
||||
margin-top: 0 !important;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<Branch entry={entry} />
|
||||
</div>
|
||||
<div className={styles.terminalClass}>{lang('End')}</div>
|
||||
</div>
|
||||
<div className={styles.terminalClass}>{lang('End')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AddNodeContextProvider>
|
||||
</ErrorBoundary>
|
||||
<div className="workflow-canvas-zoomer">
|
||||
<Slider vertical reverse defaultValue={100} step={10} min={10} value={zoom} onChange={setZoom} />
|
||||
|
@ -20,6 +20,12 @@ import useStyles from '../style';
|
||||
import { useWorkflowVariableOptions, WorkflowVariableTextArea } from '../variable';
|
||||
import { CalculationConfig } from '../components/Calculation';
|
||||
|
||||
const BRANCH_INDEX = {
|
||||
DEFAULT: null,
|
||||
ON_TRUE: 1,
|
||||
ON_FALSE: 0,
|
||||
} as const;
|
||||
|
||||
export default class extends Instruction {
|
||||
title = `{{t("Condition", { ns: "${NAMESPACE}" })}}`;
|
||||
type = 'condition';
|
||||
@ -107,18 +113,44 @@ export default class extends Instruction {
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
options = [
|
||||
{
|
||||
label: `{{t('Continue when "Yes"', { ns: "${NAMESPACE}" })}}`,
|
||||
key: 'rejectOnFalse',
|
||||
value: { rejectOnFalse: true },
|
||||
presetFieldset = {
|
||||
rejectOnFalse: {
|
||||
type: 'boolean',
|
||||
title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
enum: [
|
||||
{
|
||||
label: `{{t('Continue when "Yes"', { ns: "${NAMESPACE}" })}}`,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: `{{t('Branch into "Yes" and "No"', { ns: "${NAMESPACE}" })}}`,
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: `{{t('Branch into "Yes" and "No"', { ns: "${NAMESPACE}" })}}`,
|
||||
key: 'branch',
|
||||
value: { rejectOnFalse: false },
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
branching = ({ rejectOnFalse = true } = {}) => {
|
||||
return rejectOnFalse
|
||||
? false
|
||||
: [
|
||||
{
|
||||
label: `{{t('After end of branches', { ns: "${NAMESPACE}" })}}`,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: `{{t('Inside of "Yes" branch', { ns: "${NAMESPACE}" })}}`,
|
||||
value: BRANCH_INDEX.ON_TRUE,
|
||||
},
|
||||
{
|
||||
label: `{{t('Inside of "No" branch', { ns: "${NAMESPACE}" })}}`,
|
||||
value: BRANCH_INDEX.ON_FALSE,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
scope = {
|
||||
renderEngineReference,
|
||||
|
@ -51,27 +51,40 @@ export type NodeAvailableContext = {
|
||||
branchIndex: number;
|
||||
};
|
||||
|
||||
type Config = Record<string, any>;
|
||||
|
||||
type Options = { label: string; value: any }[];
|
||||
|
||||
export abstract class Instruction {
|
||||
title: string;
|
||||
type: string;
|
||||
group: string;
|
||||
description?: string;
|
||||
/**
|
||||
* @experimental
|
||||
* @deprecated migrate to `presetFieldset` instead
|
||||
*/
|
||||
options?: { label: string; value: any; key: string }[];
|
||||
fieldset: Record<string, ISchema>;
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
presetFieldset?: Record<string, ISchema>;
|
||||
/**
|
||||
* To presentation if the instruction is creating a branch
|
||||
* @experimental
|
||||
*/
|
||||
branching?: boolean | Options | ((config: Config) => boolean | Options);
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
view?: ISchema;
|
||||
scope?: { [key: string]: any };
|
||||
components?: { [key: string]: any };
|
||||
scope?: Record<string, any>;
|
||||
components?: Record<string, any>;
|
||||
Component?(props): JSX.Element;
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
createDefaultConfig?(): Record<string, any> {
|
||||
createDefaultConfig?(): Config {
|
||||
return {};
|
||||
}
|
||||
useVariables?(node, options?: UseVariableOptions): VariableOption;
|
||||
@ -568,12 +581,7 @@ export function NodeDefaultView(props) {
|
||||
'Node with unknown type will cause error. Please delete it or check plugin which provide this type.',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
aria-label={`_untyped-${editingTitle}`}
|
||||
className={cx(styles.nodeCardClass, 'invalid')}
|
||||
onClick={onOpenDrawer}
|
||||
>
|
||||
<div role="button" aria-label={`_untyped-${editingTitle}`} className={cx(styles.nodeCardClass, 'invalid')}>
|
||||
<div className={cx(styles.nodeMetaClass, 'workflow-node-meta')}>
|
||||
<Tag color="error">{lang('Unknown node')}</Tag>
|
||||
<span className="workflow-node-id">{data.id}</span>
|
||||
|
@ -160,6 +160,8 @@
|
||||
"Continue when \"Yes\"": "“是”则继续",
|
||||
"Branch into \"Yes\" and \"No\"": "“是”和“否”分别继续",
|
||||
"Condition expression": "条件表达式",
|
||||
"Inside of \"Yes\" branch": "“是”分支内",
|
||||
"Inside of \"No\" branch": "“否”分支内",
|
||||
"Create record": "新增数据",
|
||||
"Add new record to a collection. You can use variables from upstream nodes to assign values to fields.":
|
||||
"向一个数据表中添加新的数据。可以使用上游节点里的变量为字段赋值。",
|
||||
@ -206,5 +208,10 @@
|
||||
"Succeeded": "成功",
|
||||
"Test run": "测试执行",
|
||||
"Test run will do the actual data manipulating or API calling, please use with caution.": "测试执行会进行实际的数据操作或 API 调用,请谨慎使用。",
|
||||
"No variable": "无变量"
|
||||
"No variable": "无变量",
|
||||
|
||||
"Add node": "添加节点",
|
||||
"Move all downstream nodes to": "将所有下游节点移至",
|
||||
"After end of branches": "分支结束后",
|
||||
"Inside of branch": "分支内"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function make(name, mod) {
|
||||
}
|
||||
|
||||
export default function ({ app }) {
|
||||
app.actions({
|
||||
app.resourceManager.registerActionHandlers({
|
||||
...make('workflows', workflows),
|
||||
...make('workflows.nodes', {
|
||||
create: nodes.create,
|
||||
|
Loading…
Reference in New Issue
Block a user