1
This commit is contained in:
parent
3542ca7a7c
commit
8e5e97cb95
@ -17,6 +17,7 @@
|
|||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
@ -31,6 +32,10 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-toast": "^1.2.4",
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
|
"@react-form-builder/components-rsuite": "^7.4.0",
|
||||||
|
"@react-form-builder/core": "^7.4.0",
|
||||||
|
"@react-form-builder/designer": "^7.4.0",
|
||||||
|
"@react-form-builder/designer-bundle": "^7.4.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@types/recharts": "^1.8.29",
|
"@types/recharts": "^1.8.29",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
@ -42,6 +47,8 @@
|
|||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"form-render": "^2.5.6",
|
||||||
|
"generator-form": "^0.2.0",
|
||||||
"less": "^4.2.1",
|
"less": "^4.2.1",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.52.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -51,6 +58,7 @@
|
|||||||
"react-redux": "^9.0.4",
|
"react-redux": "^9.0.4",
|
||||||
"react-router-dom": "^6.21.0",
|
"react-router-dom": "^6.21.0",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.0",
|
||||||
|
"rsuite": "^5.83.3",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
|
|||||||
28
frontend/src/components/ui/checkbox.tsx
Normal file
28
frontend/src/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("grid place-content-center text-current")}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
@ -9,6 +9,7 @@ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import type { FlowNode, FlowNodeData, FlowEdge } from '../types';
|
import type { FlowNode, FlowNodeData, FlowEdge } from '../types';
|
||||||
import type { WorkflowNodeDefinition, JSONSchema } from '../nodes/types';
|
import type { WorkflowNodeDefinition, JSONSchema } from '../nodes/types';
|
||||||
@ -232,6 +233,36 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ 条件评估函数(支持 x-condition)
|
||||||
|
const evaluateCondition = useCallback((
|
||||||
|
currentValue: any,
|
||||||
|
expectedValue: any,
|
||||||
|
operator: string = '==='
|
||||||
|
): boolean => {
|
||||||
|
switch (operator) {
|
||||||
|
case '===':
|
||||||
|
case '==':
|
||||||
|
return currentValue === expectedValue;
|
||||||
|
case '!==':
|
||||||
|
case '!=':
|
||||||
|
return currentValue !== expectedValue;
|
||||||
|
case '>':
|
||||||
|
return Number(currentValue) > Number(expectedValue);
|
||||||
|
case '<':
|
||||||
|
return Number(currentValue) < Number(expectedValue);
|
||||||
|
case '>=':
|
||||||
|
return Number(currentValue) >= Number(expectedValue);
|
||||||
|
case '<=':
|
||||||
|
return Number(currentValue) <= Number(expectedValue);
|
||||||
|
case 'includes':
|
||||||
|
return Array.isArray(currentValue) && currentValue.includes(expectedValue);
|
||||||
|
case 'notIncludes':
|
||||||
|
return Array.isArray(currentValue) && !currentValue.includes(expectedValue);
|
||||||
|
default:
|
||||||
|
return currentValue === expectedValue;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// ✅ 通用 Select 渲染函数(减少代码重复)
|
// ✅ 通用 Select 渲染函数(减少代码重复)
|
||||||
const renderSelect = useCallback((
|
const renderSelect = useCallback((
|
||||||
options: Array<{ label: string; value: any }>,
|
options: Array<{ label: string; value: any }>,
|
||||||
@ -352,13 +383,13 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
|
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return (
|
return (
|
||||||
<input
|
<div className="flex items-center space-x-2">
|
||||||
type="checkbox"
|
<Checkbox
|
||||||
checked={field.value || false}
|
checked={field.value || false}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onChange={(e) => field.onChange(e.target.checked)}
|
onCheckedChange={field.onChange}
|
||||||
className="h-4 w-4"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -372,8 +403,8 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
}
|
}
|
||||||
}, [dataSourceCache, loadingDataSources, loading, node, renderSelect]);
|
}, [dataSourceCache, loadingDataSources, loading, node, renderSelect]);
|
||||||
|
|
||||||
// ✅ 渲染表单字段
|
// ✅ 渲染表单字段(不使用 useCallback 以支持 watch 的响应式更新)
|
||||||
const renderFormFields = useCallback((
|
const renderFormFields = (
|
||||||
schema: JSONSchema,
|
schema: JSONSchema,
|
||||||
form: ReturnType<typeof useForm>,
|
form: ReturnType<typeof useForm>,
|
||||||
prefix: string = ''
|
prefix: string = ''
|
||||||
@ -386,6 +417,22 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
const fieldName = prefix ? `${prefix}.${key}` : key;
|
const fieldName = prefix ? `${prefix}.${key}` : key;
|
||||||
const isRequired = Array.isArray(schema.required) && schema.required.includes(key);
|
const isRequired = Array.isArray(schema.required) && schema.required.includes(key);
|
||||||
|
|
||||||
|
// ✅ 检查条件显示(x-condition)- 使用 watch 订阅字段变化
|
||||||
|
if (prop['x-condition']) {
|
||||||
|
const condition = prop['x-condition'];
|
||||||
|
const conditionField = condition.field;
|
||||||
|
const conditionValue = condition.value;
|
||||||
|
const conditionOperator = condition.operator || '===';
|
||||||
|
|
||||||
|
// 获取条件字段的当前值(watch 会订阅该字段的变化,触发组件重新渲染)
|
||||||
|
const currentFieldValue = form.watch(conditionField);
|
||||||
|
|
||||||
|
// 评估条件,如果不满足则不渲染该字段
|
||||||
|
if (!evaluateCondition(currentFieldValue, conditionValue, conditionOperator)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
@ -409,7 +456,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [renderFieldControl]);
|
};
|
||||||
|
|
||||||
if (!nodeDefinition) {
|
if (!nodeDefinition) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
228
frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx
Normal file
228
frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
|
||||||
|
import {DataSourceType} from '../utils/dataSourceLoader';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批节点定义
|
||||||
|
* 用于人工审批流程,支持单人审批、多人会签、多人或签等模式
|
||||||
|
*/
|
||||||
|
export const ApprovalNodeDefinition: ConfigurableNodeDefinition = {
|
||||||
|
nodeCode: "APPROVAL",
|
||||||
|
nodeName: "审批节点",
|
||||||
|
nodeType: NodeType.APPROVAL,
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: "人工审批节点,支持多种审批模式",
|
||||||
|
|
||||||
|
// 渲染配置
|
||||||
|
renderConfig: {
|
||||||
|
shape: 'rounded-rect',
|
||||||
|
size: {width: 140, height: 48},
|
||||||
|
icon: {
|
||||||
|
type: 'emoji',
|
||||||
|
content: '✅',
|
||||||
|
size: 32
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
primary: '#10b981',
|
||||||
|
secondary: '#059669',
|
||||||
|
selectedBorder: '#3b82f6',
|
||||||
|
hoverBorder: '#10b981',
|
||||||
|
gradient: ['#ffffff', '#d1fae5']
|
||||||
|
},
|
||||||
|
handles: {
|
||||||
|
input: true,
|
||||||
|
output: true
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
showBadge: true,
|
||||||
|
showHoverMenu: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输入配置Schema
|
||||||
|
inputMappingSchema: {
|
||||||
|
type: "object",
|
||||||
|
title: "审批配置",
|
||||||
|
description: "配置审批人和审批规则",
|
||||||
|
properties: {
|
||||||
|
approvalMode: {
|
||||||
|
type: "string",
|
||||||
|
title: "审批模式",
|
||||||
|
description: "单人审批:仅需一人审批;会签:所有审批人都必须同意;或签:任一审批人同意即可",
|
||||||
|
enum: ["SINGLE", "ALL", "ANY"],
|
||||||
|
enumNames: ["单人审批", "会签(所有人同意)", "或签(任一人同意)"],
|
||||||
|
default: "SINGLE"
|
||||||
|
},
|
||||||
|
approverType: {
|
||||||
|
type: "string",
|
||||||
|
title: "审批人类型",
|
||||||
|
description: "指定审批人的方式:用户(选中的用户)、角色(拥有该角色的所有用户)、部门(该部门的所有成员)、变量(流程变量)",
|
||||||
|
enum: ["USER", "ROLE", "DEPARTMENT", "VARIABLE"],
|
||||||
|
enumNames: ["指定用户", "指定角色", "指定部门", "流程变量"],
|
||||||
|
default: "USER"
|
||||||
|
},
|
||||||
|
approvers: {
|
||||||
|
type: "array",
|
||||||
|
title: "审批人列表",
|
||||||
|
description: "选择具体的审批人。会签模式下:选中的所有用户都必须审批;或签模式下:任一用户审批即可",
|
||||||
|
items: {
|
||||||
|
type: "number"
|
||||||
|
},
|
||||||
|
'x-dataSource': DataSourceType.USERS,
|
||||||
|
'x-condition': {
|
||||||
|
field: 'approverType',
|
||||||
|
value: 'USER'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approverRoles: {
|
||||||
|
type: "array",
|
||||||
|
title: "审批角色",
|
||||||
|
description: "选择审批角色。会签模式下:拥有选中角色的所有用户都必须审批;或签模式下:任一拥有该角色的用户审批即可",
|
||||||
|
items: {
|
||||||
|
type: "number"
|
||||||
|
},
|
||||||
|
'x-dataSource': DataSourceType.ROLES,
|
||||||
|
'x-condition': {
|
||||||
|
field: 'approverType',
|
||||||
|
value: 'ROLE'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approverDepartments: {
|
||||||
|
type: "array",
|
||||||
|
title: "审批部门",
|
||||||
|
description: "选择审批部门。会签模式下:选中部门的所有成员都必须审批;或签模式下:任一部门成员审批即可",
|
||||||
|
items: {
|
||||||
|
type: "number"
|
||||||
|
},
|
||||||
|
'x-dataSource': DataSourceType.DEPARTMENTS,
|
||||||
|
'x-condition': {
|
||||||
|
field: 'approverType',
|
||||||
|
value: 'DEPARTMENT'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approverVariable: {
|
||||||
|
type: "string",
|
||||||
|
title: "审批人变量",
|
||||||
|
description: "使用流程变量指定审批人(格式:${变量名})",
|
||||||
|
'x-condition': {
|
||||||
|
field: 'approverType',
|
||||||
|
value: 'VARIABLE'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approvalTitle: {
|
||||||
|
type: "string",
|
||||||
|
title: "审批标题",
|
||||||
|
description: "审批任务的标题,支持变量",
|
||||||
|
default: "待审批:${工作流实例.title}"
|
||||||
|
},
|
||||||
|
approvalContent: {
|
||||||
|
type: "string",
|
||||||
|
title: "审批内容",
|
||||||
|
description: "审批任务的详细说明,支持变量",
|
||||||
|
'x-component': "textarea"
|
||||||
|
},
|
||||||
|
timeoutDuration: {
|
||||||
|
type: "number",
|
||||||
|
title: "超时时间(小时)",
|
||||||
|
description: "审批超时时间,0表示不限制",
|
||||||
|
default: 0,
|
||||||
|
minimum: 0
|
||||||
|
},
|
||||||
|
timeoutAction: {
|
||||||
|
type: "string",
|
||||||
|
title: "超时处理",
|
||||||
|
description: "审批超时后的处理方式",
|
||||||
|
enum: ["NONE", "AUTO_APPROVE", "AUTO_REJECT", "NOTIFY"],
|
||||||
|
enumNames: ["无操作", "自动通过", "自动拒绝", "仅通知"],
|
||||||
|
default: "NONE",
|
||||||
|
'x-condition': {
|
||||||
|
field: 'timeoutDuration',
|
||||||
|
operator: '>',
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allowDelegate: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "允许转交",
|
||||||
|
description: "是否允许审批人转交给其他人",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
allowAddSign: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "允许加签",
|
||||||
|
description: "是否允许审批人加签",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
requireComment: {
|
||||||
|
type: "boolean",
|
||||||
|
title: "必须填写意见",
|
||||||
|
description: "审批时是否必须填写审批意见",
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["approvalMode", "approverType", "approvalTitle"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 输出配置
|
||||||
|
outputs: defineNodeOutputs(
|
||||||
|
{
|
||||||
|
name: "approvalResult",
|
||||||
|
title: "审批结果",
|
||||||
|
type: "string",
|
||||||
|
enum: ["APPROVED", "REJECTED", "TIMEOUT"],
|
||||||
|
description: "审批的最终结果",
|
||||||
|
example: "APPROVED",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approver",
|
||||||
|
title: "审批人",
|
||||||
|
type: "string",
|
||||||
|
description: "实际执行审批的用户",
|
||||||
|
example: "张三",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approverUserId",
|
||||||
|
title: "审批人ID",
|
||||||
|
type: "number",
|
||||||
|
description: "审批人的用户ID",
|
||||||
|
example: 1001,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approvalTime",
|
||||||
|
title: "审批时间",
|
||||||
|
type: "string",
|
||||||
|
description: "审批完成的时间",
|
||||||
|
example: "2025-10-23T10:30:00Z",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approvalComment",
|
||||||
|
title: "审批意见",
|
||||||
|
type: "string",
|
||||||
|
description: "审批人填写的意见",
|
||||||
|
example: "同意发布到生产环境",
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approvalDuration",
|
||||||
|
title: "审批用时(秒)",
|
||||||
|
type: "number",
|
||||||
|
description: "从创建到完成审批的时长",
|
||||||
|
example: 3600,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allApprovers",
|
||||||
|
title: "所有审批人",
|
||||||
|
type: "array",
|
||||||
|
description: "参与审批的所有人员列表(会签/或签时),格式:[{userId, userName, result, comment, time}]",
|
||||||
|
example: [{userId: 1001, userName: "张三", result: "APPROVED", comment: "同意", time: "2025-10-23T10:30:00Z"}],
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types';
|
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
|
||||||
import {DataSourceType} from '../utils/dataSourceLoader';
|
import {DataSourceType} from '../utils/dataSourceLoader';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +59,7 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
},
|
},
|
||||||
required: ["jenkinsServerId"]
|
required: ["jenkinsServerId"]
|
||||||
},
|
},
|
||||||
outputs: [
|
outputs: defineNodeOutputs(
|
||||||
{
|
{
|
||||||
name: "buildNumber",
|
name: "buildNumber",
|
||||||
title: "构建编号",
|
title: "构建编号",
|
||||||
@ -68,15 +68,6 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
example: 123,
|
example: 123,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "status",
|
|
||||||
title: "构建状态",
|
|
||||||
type: "string",
|
|
||||||
enum: ["SUCCESS", "FAILURE"],
|
|
||||||
description: "构建执行的结果状态",
|
|
||||||
example: "SUCCESS",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "buildUrl",
|
name: "buildUrl",
|
||||||
title: "构建URL",
|
title: "构建URL",
|
||||||
@ -109,7 +100,7 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
example: 120,
|
example: 120,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types';
|
import {ConfigurableNodeDefinition, NodeType, NodeCategory, defineNodeOutputs} from './types';
|
||||||
import {DataSourceType} from "@/pages/Workflow/Design/utils/dataSourceLoader.ts";
|
import {DataSourceType} from "@/pages/Workflow/Design/utils/dataSourceLoader.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,13 +80,6 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
},
|
},
|
||||||
required: ["notificationChannel", "title", "content"]
|
required: ["notificationChannel", "title", "content"]
|
||||||
},
|
},
|
||||||
outputs: [{
|
outputs: defineNodeOutputs()
|
||||||
name: "status",
|
|
||||||
title: "执行状态",
|
|
||||||
type: "string",
|
|
||||||
enum: ["SUCCESS", "FAILURE"],
|
|
||||||
description: "执行的结果状态",
|
|
||||||
example: "SUCCESS"
|
|
||||||
}]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { StartEventNodeDefinition } from './StartEventNode';
|
|||||||
import { EndEventNodeDefinition } from './EndEventNode';
|
import { EndEventNodeDefinition } from './EndEventNode';
|
||||||
import { JenkinsBuildNodeDefinition } from './JenkinsBuildNode';
|
import { JenkinsBuildNodeDefinition } from './JenkinsBuildNode';
|
||||||
import { NotificationNodeDefinition } from './NotificationNode';
|
import { NotificationNodeDefinition } from './NotificationNode';
|
||||||
|
import { ApprovalNodeDefinition } from './ApprovalNode';
|
||||||
import type { WorkflowNodeDefinition } from './types';
|
import type { WorkflowNodeDefinition } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +20,7 @@ export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
|
|||||||
EndEventNodeDefinition,
|
EndEventNodeDefinition,
|
||||||
JenkinsBuildNodeDefinition,
|
JenkinsBuildNodeDefinition,
|
||||||
NotificationNodeDefinition,
|
NotificationNodeDefinition,
|
||||||
|
ApprovalNodeDefinition,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,6 +32,7 @@ export const nodeTypes = {
|
|||||||
END_EVENT: BaseNode,
|
END_EVENT: BaseNode,
|
||||||
JENKINS_BUILD: BaseNode,
|
JENKINS_BUILD: BaseNode,
|
||||||
NOTIFICATION: BaseNode,
|
NOTIFICATION: BaseNode,
|
||||||
|
APPROVAL: BaseNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,6 +47,7 @@ export {
|
|||||||
EndEventNodeDefinition,
|
EndEventNodeDefinition,
|
||||||
JenkinsBuildNodeDefinition,
|
JenkinsBuildNodeDefinition,
|
||||||
NotificationNodeDefinition,
|
NotificationNodeDefinition,
|
||||||
|
ApprovalNodeDefinition,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导出类型
|
// 导出类型
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export enum NodeType {
|
|||||||
DEPLOY_NODE = 'DEPLOY_NODE',
|
DEPLOY_NODE = 'DEPLOY_NODE',
|
||||||
JENKINS_BUILD = 'JENKINS_BUILD',
|
JENKINS_BUILD = 'JENKINS_BUILD',
|
||||||
NOTIFICATION = 'NOTIFICATION',
|
NOTIFICATION = 'NOTIFICATION',
|
||||||
|
APPROVAL = 'APPROVAL',
|
||||||
GATEWAY_NODE = 'GATEWAY_NODE',
|
GATEWAY_NODE = 'GATEWAY_NODE',
|
||||||
SUB_PROCESS = 'SUB_PROCESS',
|
SUB_PROCESS = 'SUB_PROCESS',
|
||||||
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
CALL_ACTIVITY = 'CALL_ACTIVITY'
|
||||||
@ -31,6 +32,7 @@ export const NODE_CATEGORY_MAP: Record<NodeType, NodeCategory> = {
|
|||||||
[NodeType.DEPLOY_NODE]: NodeCategory.TASK,
|
[NodeType.DEPLOY_NODE]: NodeCategory.TASK,
|
||||||
[NodeType.JENKINS_BUILD]: NodeCategory.TASK,
|
[NodeType.JENKINS_BUILD]: NodeCategory.TASK,
|
||||||
[NodeType.NOTIFICATION]: NodeCategory.TASK,
|
[NodeType.NOTIFICATION]: NodeCategory.TASK,
|
||||||
|
[NodeType.APPROVAL]: NodeCategory.TASK,
|
||||||
[NodeType.GATEWAY_NODE]: NodeCategory.GATEWAY,
|
[NodeType.GATEWAY_NODE]: NodeCategory.GATEWAY,
|
||||||
[NodeType.SUB_PROCESS]: NodeCategory.CONTAINER,
|
[NodeType.SUB_PROCESS]: NodeCategory.CONTAINER,
|
||||||
[NodeType.CALL_ACTIVITY]: NodeCategory.CONTAINER,
|
[NodeType.CALL_ACTIVITY]: NodeCategory.CONTAINER,
|
||||||
@ -84,6 +86,55 @@ export interface OutputField {
|
|||||||
required?: boolean; // 是否必定产生(默认 true)
|
required?: boolean; // 是否必定产生(默认 true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础输出字段 - 所有节点都会产生的通用字段
|
||||||
|
* 类似于 Java 中的继承,每个节点都会自动拥有这些字段
|
||||||
|
*/
|
||||||
|
export const BASE_OUTPUT_FIELDS: OutputField[] = [
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
title: "执行状态",
|
||||||
|
type: "string",
|
||||||
|
enum: ["SUCCESS", "FAILURE"],
|
||||||
|
description: "节点执行的最终状态",
|
||||||
|
example: "SUCCESS",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具函数:定义节点输出字段
|
||||||
|
* 类似于 Java 的继承,自动合并基础字段和节点特有字段
|
||||||
|
*
|
||||||
|
* @param customFields - 节点特有的输出字段
|
||||||
|
* @returns 包含基础字段和自定义字段的完整输出列表
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* outputs: defineNodeOutputs(
|
||||||
|
* {
|
||||||
|
* name: "buildNumber",
|
||||||
|
* title: "构建编号",
|
||||||
|
* type: "number",
|
||||||
|
* description: "Jenkins 构建编号",
|
||||||
|
* example: 123,
|
||||||
|
* required: true
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: "buildUrl",
|
||||||
|
* title: "构建URL",
|
||||||
|
* type: "string",
|
||||||
|
* description: "Jenkins构建页面的访问地址",
|
||||||
|
* example: "http://jenkins.example.com/job/app/123/",
|
||||||
|
* required: true
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const defineNodeOutputs = (...customFields: OutputField[]): OutputField[] => {
|
||||||
|
return [...BASE_OUTPUT_FIELDS, ...customFields];
|
||||||
|
};
|
||||||
|
|
||||||
// ========== 渲染配置(配置驱动节点渲染) ==========
|
// ========== 渲染配置(配置驱动节点渲染) ==========
|
||||||
|
|
||||||
// 节点尺寸
|
// 节点尺寸
|
||||||
|
|||||||
@ -9,7 +9,10 @@ export enum DataSourceType {
|
|||||||
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
|
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
|
||||||
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
|
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
|
||||||
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
|
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
|
||||||
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS'
|
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS',
|
||||||
|
USERS = 'USERS',
|
||||||
|
ROLES = 'ROLES',
|
||||||
|
DEPARTMENTS = 'DEPARTMENTS'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,6 +100,39 @@ export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
|||||||
url: item.url
|
url: item.url
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[DataSourceType.USERS]: {
|
||||||
|
url: '/api/v1/system/users/page',
|
||||||
|
params: {pageSize: 1000},
|
||||||
|
transform: (data: any) => {
|
||||||
|
const users = data.content || data;
|
||||||
|
return users.map((item: any) => ({
|
||||||
|
label: `${item.name} (${item.username})`,
|
||||||
|
value: item.id,
|
||||||
|
username: item.username,
|
||||||
|
email: item.email
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[DataSourceType.ROLES]: {
|
||||||
|
url: '/api/v1/system/roles/list',
|
||||||
|
transform: (data: any[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
code: item.code
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[DataSourceType.DEPARTMENTS]: {
|
||||||
|
url: '/api/v1/system/departments/list',
|
||||||
|
transform: (data: any[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
code: item.code
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user