Feat(plugin workflow): refactor calculation and add filter for query (#264)
* feat(plugin-workflow): group nodes in add button dropdown menu and adjust some style * fix(client): add missing key in component * feat(plugin-workflow): add job type variable getter structure for calculation * feat(plugin-workflow): add calculation config for query filter
This commit is contained in:
parent
cdfc418f39
commit
1dc8a21cfe
@ -127,7 +127,7 @@ export const AddFieldAction = () => {
|
||||
{options.map((option) => {
|
||||
return (
|
||||
option.children.length > 0 && (
|
||||
<Menu.ItemGroup title={compile(option.label)}>
|
||||
<Menu.ItemGroup key={option.label} title={compile(option.label)}>
|
||||
{option.children.map((child) => {
|
||||
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
|
||||
})}
|
||||
|
@ -77,14 +77,7 @@ export function Branch({
|
||||
{controller}
|
||||
<AddButton upstream={from} branchIndex={branchIndex} />
|
||||
<div className="workflow-node-list">
|
||||
{list.map(item => {
|
||||
return (
|
||||
<div key={item.id} className={cx(nodeBlockClass)}>
|
||||
<Node data={item} />
|
||||
<AddButton upstream={item} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{list.map(item => <Node data={item} key={item.id} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -98,7 +91,7 @@ interface AddButtonProps {
|
||||
branchIndex?: number;
|
||||
};
|
||||
|
||||
function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
export function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
const { resource } = useCollection();
|
||||
const { data } = useResourceActionContext();
|
||||
const { onNodeAdded } = useFlowContext();
|
||||
@ -125,20 +118,30 @@ function AddButton({ upstream, branchIndex = null }: AddButtonProps) {
|
||||
onNodeAdded(node);
|
||||
}
|
||||
|
||||
const groups = [
|
||||
{ value: 'control', name: '流程控制' },
|
||||
{ value: 'model', name: '数据表操作' },
|
||||
];
|
||||
const instructionList = (Array.from(instructions.getValues()) as Instruction[]);
|
||||
|
||||
return (
|
||||
<div className={cx(addButtonClass)}>
|
||||
<Dropdown trigger={['click']} overlay={
|
||||
<Menu onClick={ev => onCreate(ev)}>
|
||||
{(Array.from(instructions.getValues()) as Instruction[]).map(item => item.options
|
||||
? (
|
||||
<Menu.SubMenu key={item.type} title={item.title}>
|
||||
{item.options.map(option => (
|
||||
<Menu.Item key={option.key}>{option.label}</Menu.Item>
|
||||
{groups.map(group => (
|
||||
<Menu.ItemGroup key={group.value} title={group.name}>
|
||||
{instructionList.filter(item => item.group === group.value).map(item => item.options
|
||||
? (
|
||||
<Menu.SubMenu key={item.type} title={item.title}>
|
||||
{item.options.map(option => (
|
||||
<Menu.Item key={option.key}>{option.label}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
: (
|
||||
<Menu.Item key={item.type}>{item.title}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
: (
|
||||
<Menu.Item key={item.type}>{item.title}</Menu.Item>
|
||||
</Menu.ItemGroup>
|
||||
))}
|
||||
</Menu>
|
||||
}>
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React from "react";
|
||||
import { Input, Select } from "antd";
|
||||
import { Cascader, DatePicker, Input, InputNumber, Select } from "antd";
|
||||
import { css } from "@emotion/css";
|
||||
|
||||
import { useNodeContext } from "./nodes";
|
||||
|
||||
import { instructions, useNodeContext } from "./nodes";
|
||||
import { useFlowContext } from "./WorkflowCanvas";
|
||||
|
||||
function NullRender() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const calculators = [
|
||||
{ value: 'equal', name: '等于' },
|
||||
@ -13,67 +16,219 @@ export const calculators = [
|
||||
|
||||
const JT_VALUE_RE = /^\s*\{\{([\s\S]*)\}\}\s*$/;
|
||||
|
||||
function JobSelect({ value, onChange }) {
|
||||
const node = useNodeContext();
|
||||
const stack = [];
|
||||
for (let current = node.upstream; current; current = current.upstream) {
|
||||
stack.push(current);
|
||||
export function parseStringValue(value: string, Types) {
|
||||
const matcher = value.match(JT_VALUE_RE);
|
||||
if (!matcher) {
|
||||
return { type: 'constant', value, options: { type: 'string' } };
|
||||
}
|
||||
return (
|
||||
<Select value={value} onChange={onChange}>
|
||||
{stack.map(item => (
|
||||
<Select.Option key={item.id} value={item.id}>{item.title ?? `#${item.id}`}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
|
||||
const [type, ...paths] = matcher[1].split('.');
|
||||
|
||||
return {
|
||||
type,
|
||||
options: paths.length ? (Types || VariableTypes)[type].parse(paths) : {}
|
||||
};
|
||||
}
|
||||
|
||||
function ContextSelect({ value, onChange }) {
|
||||
return (
|
||||
<Select></Select>
|
||||
);
|
||||
}
|
||||
|
||||
const VariableTypeComponent = {
|
||||
constant({ onChange, ...props }) {
|
||||
return <Input {...props} onChange={ev => onChange(ev.target.value)} />
|
||||
const ConstantTypes = {
|
||||
string: {
|
||||
title: '字符串',
|
||||
value: 'string',
|
||||
component({ onChange, type, options, ...props }) {
|
||||
return <Input {...props} onChange={ev => onChange(ev.target.value)} />;
|
||||
},
|
||||
default: ''
|
||||
},
|
||||
job: JobSelect,
|
||||
context: ContextSelect,
|
||||
number: {
|
||||
title: '数字',
|
||||
value: 'number',
|
||||
component({ type, options, ...props }) {
|
||||
return <InputNumber {...props} />;
|
||||
},
|
||||
default: 0
|
||||
},
|
||||
boolean: {
|
||||
title: '逻辑值',
|
||||
value: 'boolean',
|
||||
component({ type, options, ...props }) {
|
||||
return (
|
||||
<Select {...props}>
|
||||
<Select.Option value={true}>真</Select.Option>
|
||||
<Select.Option value={false}>假</Select.Option>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
default: false
|
||||
},
|
||||
// date: {
|
||||
// title: '日期',
|
||||
// value: 'date',
|
||||
// component({ type, options, ...props }) {
|
||||
// return <DatePicker {...props} />;
|
||||
// },
|
||||
// default: new Date()
|
||||
// }
|
||||
};
|
||||
|
||||
export const VariableTypes = {
|
||||
constant: {
|
||||
title: '常量',
|
||||
value: 'constant',
|
||||
options: Object.values(ConstantTypes).map(item => ({
|
||||
value: item.value,
|
||||
label: item.title
|
||||
})),
|
||||
component({ options: { type } = { type: 'string' } }) {
|
||||
return type ? ConstantTypes[type].component : NullRender;
|
||||
},
|
||||
appendTypeValue({ options = { type: 'string' } }) {
|
||||
return options?.type ? [options.type] : [];
|
||||
},
|
||||
onTypeChange(props, [type, optionsType], onChange) {
|
||||
const { default: value } = ConstantTypes[optionsType];
|
||||
onChange({
|
||||
value,
|
||||
type,
|
||||
options: { ...props.options, type: optionsType }
|
||||
});
|
||||
},
|
||||
parse(path) {
|
||||
return { path };
|
||||
}
|
||||
},
|
||||
job: {
|
||||
title: '节点数据',
|
||||
value: 'job',
|
||||
options() {
|
||||
const node = useNodeContext();
|
||||
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
|
||||
if (getter) {
|
||||
stack.push({
|
||||
value: current.id,
|
||||
label: current.title ?? `#${current.id}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
component({ options }) {
|
||||
const { nodes } = useFlowContext();
|
||||
if (!options?.nodeId) {
|
||||
return NullRender;
|
||||
}
|
||||
const node = nodes.find(n => n.id == options.nodeId);
|
||||
if (!node) {
|
||||
return NullRender;
|
||||
}
|
||||
const instruction = instructions.get(node.type);
|
||||
return instruction?.getter ?? NullRender;
|
||||
},
|
||||
appendTypeValue({ options = {} }: { type: string, options: any }) {
|
||||
return options.nodeId ? [Number.parseInt(options.nodeId, 10)] : [];
|
||||
},
|
||||
onTypeChange(props, [type, nodeId], onChange) {
|
||||
onChange({
|
||||
...props,
|
||||
type,
|
||||
options: { ...props.options, nodeId }
|
||||
});
|
||||
},
|
||||
parse([nodeId, ...path]) {
|
||||
return { nodeId, path: path.join('.') };
|
||||
},
|
||||
stringify({ options }) {
|
||||
const stack = ['job'];
|
||||
if (options.nodeId) {
|
||||
stack.push(options.nodeId);
|
||||
if (options.path) {
|
||||
stack.push(options.path);
|
||||
}
|
||||
}
|
||||
return `{{${stack.join('.')}}}`;
|
||||
}
|
||||
},
|
||||
// context: ContextSelect,
|
||||
// calculation: Calculation
|
||||
};
|
||||
|
||||
function OperandTypeSelect({ value, onChange }) {
|
||||
return (
|
||||
<Select value={value} onChange={onChange} placeholder="变量来源">
|
||||
<Select.Option value="constant">常量</Select.Option>
|
||||
<Select.Option value="job">节点数据</Select.Option>
|
||||
<Select.Option value="context" disabled>触发数据</Select.Option>
|
||||
<Select.Option value="calculation" disabled>计算</Select.Option>
|
||||
</Select>
|
||||
);
|
||||
interface OperandProps {
|
||||
value: {
|
||||
type: string;
|
||||
value?: any;
|
||||
options?: any;
|
||||
};
|
||||
onChange(v: any): void
|
||||
}
|
||||
|
||||
export function Operand({ value: operand = { type: 'constant', value: '' }, onChange }) {
|
||||
const { value } = operand;
|
||||
let { type = 'constant' } = operand;
|
||||
if (typeof value === 'string') {
|
||||
const matcher = value.match(JT_VALUE_RE);
|
||||
export function Operand({ onChange, value: operand = { type: 'constant', value: '', options: { type: 'string' } } }: OperandProps) {
|
||||
const { type } = operand;
|
||||
|
||||
if (matcher) {
|
||||
console.log(matcher);
|
||||
}
|
||||
}
|
||||
|
||||
const VariableComponent = VariableTypeComponent[type];
|
||||
const { component, appendTypeValue } = VariableTypes[type];
|
||||
const VariableComponent = typeof component === 'function' ? component(operand) : component;
|
||||
|
||||
return (
|
||||
<div className={css`
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
align-items: center;
|
||||
`}>
|
||||
<OperandTypeSelect value={type} onChange={v => onChange({ ...operand, type: v })} />
|
||||
<VariableComponent value={value} onChange={v => onChange({ ...operand, value: v })} />
|
||||
<Cascader
|
||||
allowClear={false}
|
||||
value={[type, ...(appendTypeValue ? appendTypeValue(operand) : [])]}
|
||||
options={Object.values(VariableTypes).map(item => {
|
||||
const children = typeof item.options === 'function' ? item.options() : item.options;
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.value,
|
||||
children,
|
||||
disabled: children && !children.length
|
||||
};
|
||||
})}
|
||||
onChange={(t: Array<string | number>) => {
|
||||
const { onTypeChange } = VariableTypes[t[0]];
|
||||
if (typeof onTypeChange === 'function') {
|
||||
onTypeChange(operand, t, onChange);
|
||||
} else {
|
||||
if (t[0] !== type) {
|
||||
onChange({ type: t[0], value: null });
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<VariableComponent {...operand} onChange={v => onChange({ ...operand, value: v })} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Calculation({ calculator, operands, onChange }) {
|
||||
return (
|
||||
<div className={css`
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
align-items: center;
|
||||
|
||||
.ant-select{
|
||||
width: auto;
|
||||
}
|
||||
`}>
|
||||
<Operand value={operands[0]} onChange={(v => onChange({ calculator, operands: [v, operands[1]] }))} />
|
||||
{operands[0]
|
||||
? (
|
||||
<>
|
||||
<Select value={calculator} onChange={v => onChange({ operands, calculator: v })}>
|
||||
{calculators.map(item => (
|
||||
<Select.Option key={item.value} value={item.value}>{item.name}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Operand value={operands[1]} onChange={(v => onChange({ calculator, operands: [operands[0], v] }))} />
|
||||
</>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { Trans } from "react-i18next";
|
||||
import { NodeDefaultView } from ".";
|
||||
import { Branch, useFlowContext } from "../WorkflowCanvas";
|
||||
import { branchBlockClass, nodeSubtreeClass } from "../style";
|
||||
import { calculators, Operand } from "../calculators";
|
||||
import { Calculation } from "../calculators";
|
||||
// import { SchemaComponent } from "../../schema-component";
|
||||
|
||||
function CalculationItem({ value, onChange, onRemove }) {
|
||||
@ -30,24 +30,7 @@ function CalculationItem({ value, onChange, onRemove }) {
|
||||
onChange={group => onChange({ ...value, group })}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={css`
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
|
||||
.ant-select{
|
||||
width: auto;
|
||||
}
|
||||
`}>
|
||||
<Operand value={operands[0]} onChange={v => onChange({ ...value, operands: [v, operands[1]] })} />
|
||||
<Select value={calculator} onChange={v => onChange({ ...value, calculator: v })}>
|
||||
{calculators.map(item => (
|
||||
<Select.Option key={item.value} value={item.value}>{item.name}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Operand value={operands[1]} onChange={v => onChange({ ...value, operands: [operands[0], v] })} />
|
||||
</div>
|
||||
)
|
||||
: <Calculation operands={operands} calculator={calculator} onChange={onChange} />
|
||||
}
|
||||
<Button onClick={onRemove} type="text" icon={<CloseCircleOutlined />} />
|
||||
</div>
|
||||
@ -146,6 +129,7 @@ function CalculationConfig({ value, onChange }) {
|
||||
export default {
|
||||
title: '条件判断',
|
||||
type: 'condition',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
rejectOnFalse: {
|
||||
type: 'boolean',
|
||||
|
@ -1,15 +1,15 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { cx } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Button, Modal, Tag } from 'antd';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { DeleteOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
|
||||
import { Registry } from '@nocobase/utils';
|
||||
|
||||
import { SchemaComponent, useActionContext, useAPIClient, useCollection, useRequest, useResourceActionContext } from '../..';
|
||||
import { useFlowContext } from '../WorkflowCanvas';
|
||||
import { AddButton, useFlowContext } from '../WorkflowCanvas';
|
||||
|
||||
import { nodeClass, nodeCardClass, nodeHeaderClass, nodeTitleClass } from '../style';
|
||||
import { nodeClass, nodeCardClass, nodeHeaderClass, nodeTitleClass, nodeBlockClass } from '../style';
|
||||
|
||||
import query from './query';
|
||||
import condition from './condition';
|
||||
@ -44,12 +44,15 @@ function useUpdateConfigAction() {
|
||||
export interface Instruction {
|
||||
title: string;
|
||||
type: string;
|
||||
group: string;
|
||||
options?: { label: string; value: any; key: string }[];
|
||||
fieldset: { [key: string]: ISchema };
|
||||
view: ISchema;
|
||||
view?: ISchema;
|
||||
scope?: { [key: string]: any };
|
||||
components?: { [key: string]: any }
|
||||
render?(props): React.ReactElement
|
||||
components?: { [key: string]: any };
|
||||
render?(props): React.ReactElement;
|
||||
endding?: boolean;
|
||||
getter?(node: any): React.ReactElement;
|
||||
};
|
||||
|
||||
export const instructions = new Registry<Instruction>();
|
||||
@ -67,12 +70,38 @@ export function useNodeContext() {
|
||||
export function Node({ data }) {
|
||||
const instruction = instructions.get(data.type);
|
||||
|
||||
if (instruction.render) {
|
||||
return instruction.render(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeDefaultView data={data} />
|
||||
<div className={cx(nodeBlockClass)}>
|
||||
{instruction.render
|
||||
? instruction.render(data)
|
||||
: <NodeDefaultView data={data} />
|
||||
}
|
||||
{!instruction.endding
|
||||
? <AddButton upstream={data} />
|
||||
: (
|
||||
<div
|
||||
className={css`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1px;
|
||||
height: 6em;
|
||||
padding: 2em 0;
|
||||
background-color: #f0f2f5;
|
||||
|
||||
.anticon{
|
||||
font-size: 1.5em;
|
||||
line-height: 100%;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -95,7 +124,7 @@ export function RemoveButton() {
|
||||
|
||||
Modal.confirm({
|
||||
title: '删除分支',
|
||||
content: '节点包含分支,将删除其所有分支下的子节点,确定继续?',
|
||||
content: '节点包含分支,将同时删除其所有分支下的子节点,确定继续?',
|
||||
onOk
|
||||
});
|
||||
}
|
||||
@ -123,7 +152,7 @@ export function NodeDefaultView(props) {
|
||||
</div>
|
||||
<SchemaComponent
|
||||
scope={instruction.scope}
|
||||
components={{...instruction.components}}
|
||||
components={instruction.components}
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
|
@ -11,6 +11,7 @@ import { Button, Tooltip } from "antd";
|
||||
export default {
|
||||
title: '并行',
|
||||
type: 'parallel',
|
||||
group: 'control',
|
||||
fieldset: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
@ -39,7 +40,7 @@ export default {
|
||||
return result.concat(node);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}, []).sort((a, b) => a.branchIndex - b.branchIndex);
|
||||
const [branchCount, setBranchCount] = useState(Math.max(2, branches.length));
|
||||
|
||||
const tempBranches = Array(Math.max(0, branchCount - branches.length)).fill(null);
|
||||
|
@ -1,10 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useForm } from '@formily/react';
|
||||
import { action } from '@formily/reactive';
|
||||
import { Cascader, Select } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { useCollectionManager } from '../../collection-manager';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { useRequest, useCollectionManager } from '../..';
|
||||
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
|
||||
import { useFlowContext } from '../WorkflowCanvas';
|
||||
import { parseStringValue, VariableTypes } from '../calculators';
|
||||
|
||||
const BaseTypeSet = new Set(['boolean', 'number', 'string', 'date']);
|
||||
|
||||
export default {
|
||||
title: '数据查询',
|
||||
type: 'query',
|
||||
group: 'model',
|
||||
fieldset: {
|
||||
collection: {
|
||||
type: 'string',
|
||||
@ -20,7 +31,42 @@ export default {
|
||||
title: '多条数据',
|
||||
name: 'multiple',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox'
|
||||
'x-component': 'Checkbox',
|
||||
'x-component-props': {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
params: {
|
||||
type: 'object',
|
||||
name: 'params',
|
||||
title: '查询参数',
|
||||
'x-decorator': 'FormItem',
|
||||
properties: {
|
||||
filter: {
|
||||
type: 'object',
|
||||
title: '筛选条件',
|
||||
name: 'filter',
|
||||
'x-decorator': 'div',
|
||||
'x-decorator-props': {
|
||||
className: css`
|
||||
.ant-select{
|
||||
width: auto;
|
||||
}
|
||||
`
|
||||
},
|
||||
'x-component': 'Filter',
|
||||
'x-component-props': {
|
||||
useDataSource(options) {
|
||||
const { values } = useForm();
|
||||
const data = useCollectionFilterOptions(values.collection);
|
||||
return useRequest(() => Promise.resolve({
|
||||
data
|
||||
}), options)
|
||||
},
|
||||
dynamicComponent: 'VariableComponent'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
view: {
|
||||
@ -38,5 +84,80 @@ export default {
|
||||
})(collections);
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
VariableComponent({ value, onChange, renderSchemaComponent }) {
|
||||
const VTypes = { ...VariableTypes,
|
||||
constant: {
|
||||
title: '常量',
|
||||
value: 'constant',
|
||||
options: undefined
|
||||
}
|
||||
};
|
||||
|
||||
const operand = typeof value === 'string'
|
||||
? 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 (
|
||||
<div className={css`
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
align-items: center;
|
||||
`}>
|
||||
<Cascader
|
||||
allowClear={false}
|
||||
value={types}
|
||||
options={Object.values(VTypes).map(item => ({
|
||||
label: item.title,
|
||||
value: item.value,
|
||||
children: typeof item.options === 'function' ? item.options() : item.options
|
||||
}))}
|
||||
onChange={(next: Array<any>) => {
|
||||
const { onTypeChange, stringify } = VTypes[next[0]];
|
||||
setTypes(next);
|
||||
if (typeof onTypeChange === 'function') {
|
||||
onTypeChange(operand, next, (op) => {
|
||||
onChange(stringify(op));
|
||||
});
|
||||
} else {
|
||||
if (next[0] !== type) {
|
||||
onChange(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{type === 'constant'
|
||||
? renderSchemaComponent()
|
||||
: <VariableComponent {...operand} onChange={(v) => {
|
||||
const { stringify } = VTypes[type];
|
||||
onChange(stringify(v));
|
||||
}} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
getter({ 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 (
|
||||
<Select value={options.path} placeholder="选择字段" onChange={path => onChange({ options: { ...options, path } })}>
|
||||
{collection.fields
|
||||
.filter(field => BaseTypeSet.has(field.uiSchema.type))
|
||||
.map(field => (
|
||||
<Select.Option key={field.name} value={field.name}>{t(field.uiSchema.title)}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -88,6 +88,8 @@ export const branchClass = css`
|
||||
`;
|
||||
|
||||
export const nodeBlockClass = css`
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@ -95,6 +97,7 @@ export const nodeBlockClass = css`
|
||||
`;
|
||||
|
||||
export const nodeClass = css`
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
Loading…
Reference in New Issue
Block a user