This commit is contained in:
dengqichen 2025-10-22 15:50:48 +08:00
parent 6b2e127eaa
commit 4fded64d7c
6 changed files with 100 additions and 15 deletions

View File

@ -51,6 +51,9 @@ const HighlightLayer: React.FC<HighlightLayerProps> = ({ value, className }) =>
textDecorationColor: 'hsl(199 89% 48% / 0.6)', textDecorationColor: 'hsl(199 89% 48% / 0.6)',
textDecorationThickness: '2px', textDecorationThickness: '2px',
textUnderlineOffset: '2px', textUnderlineOffset: '2px',
// 确保垂直对齐
display: 'inline',
verticalAlign: 'baseline',
}} }}
> >
{match[0]} {match[0]}

View File

@ -142,6 +142,8 @@ const VariableInput: React.FC<VariableInputProps> = ({
highlightElement.style.whiteSpace = variant === 'textarea' ? 'pre-wrap' : 'nowrap'; highlightElement.style.whiteSpace = variant === 'textarea' ? 'pre-wrap' : 'nowrap';
highlightElement.style.wordWrap = 'break-word'; highlightElement.style.wordWrap = 'break-word';
highlightElement.style.overflowWrap = inputStyles.overflowWrap; highlightElement.style.overflowWrap = inputStyles.overflowWrap;
highlightElement.style.verticalAlign = inputStyles.verticalAlign;
highlightElement.style.boxSizing = inputStyles.boxSizing;
}; };
// 同步滚动位置(仅 textarea // 同步滚动位置(仅 textarea
@ -443,10 +445,14 @@ const VariableInput: React.FC<VariableInputProps> = ({
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none"
style={{ style={{
overflow: 'hidden', overflow: 'hidden',
display: variant === 'input' ? 'flex' : 'block',
alignItems: variant === 'input' ? 'center' : 'flex-start',
}} }}
> >
<div style={{ width: '100%', flex: variant === 'input' ? 'none' : undefined }}>
<HighlightLayer value={value} /> <HighlightLayer value={value} />
</div> </div>
</div>
)} )}
</div> </div>

View File

@ -68,12 +68,12 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
inputMappingSchema: { inputMappingSchema: {
type: "object", type: "object",
title: "输入", title: "输入",
description: "从上游节点接收的数据映射配置", description: "当前节点所需数据配置",
properties: { properties: {
jenkinsServerId: { jenkinsServerId: {
type: "number", type: "number",
title: "Jenkins服务器", title: "服务器",
description: "选择要使用的Jenkins服务器", description: "选择要使用的服务器",
'x-dataSource': DataSourceType.JENKINS_SERVERS 'x-dataSource': DataSourceType.JENKINS_SERVERS
}, },
project: { project: {

View File

@ -1,4 +1,5 @@
import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types'; import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types';
import {DataSourceType} from "@/pages/Workflow/Design/utils/dataSourceLoader.ts";
/** /**
* *
@ -60,14 +61,28 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
title: "节点描述", title: "节点描述",
description: "节点的详细说明", description: "节点的详细说明",
default: "发送通知消息到指定渠道" default: "发送通知消息到指定渠道"
}
}, },
required: ["nodeName", "nodeCode", "notificationType", "title", "content"]
},
inputMappingSchema: {
type: "object",
title: "输入",
description: "当前节点所需数据配置",
properties: {
// notificationType: {
// type: "string",
// title: "通知类型",
// description: "选择通知发送的渠道",
// enum: ["EMAIL", "DINGTALK", "WECHAT_WORK", "SMS"],
// enumNames: ["邮件", "钉钉", "企业微信", "短信"],
// default: "EMAIL"
// },
notificationType: { notificationType: {
type: "string", type: "string",
title: "通知类型", title: "通知类型",
description: "选择通知发送的渠道", description: "选择通知发送的渠道",
enum: ["EMAIL", "DINGTALK", "WECHAT_WORK", "SMS"], 'x-dataSource': DataSourceType.NOTIFICATION_CHANNEL_TYPES
enumNames: ["邮件", "钉钉", "企业微信", "短信"],
default: "EMAIL"
}, },
title: { title: {
type: "string", type: "string",
@ -83,7 +98,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
default: "" default: ""
} }
}, },
required: ["nodeName", "nodeCode", "notificationType", "title", "content"] required: ["jenkinsServerId"]
}, },
outputs: [{ outputs: [{
name: "status", name: "status",

View File

@ -2,6 +2,7 @@ import React from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react'; import { Handle, Position, NodeProps } from '@xyflow/react';
import type { FlowNodeData } from '../../types'; import type { FlowNodeData } from '../../types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { isConfigurableNode } from '../types';
/** /**
* BaseNode - shadcn/ui * BaseNode - shadcn/ui
@ -17,6 +18,52 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
const config = definition.renderConfig; const config = definition.renderConfig;
// 渲染输入字段标签(来自 inputMappingSchema
const renderInputSection = () => {
if (!isConfigurableNode(definition) || !definition.inputMappingSchema) {
return null;
}
const schema = definition.inputMappingSchema;
// 获取所有定义的输入字段(从 schema
if (!schema.properties) {
return null;
}
const allInputs = Object.keys(schema.properties).map(key => {
const fieldSchema = schema.properties![key];
return {
key,
title: fieldSchema.title || key,
description: fieldSchema.description,
};
});
if (allInputs.length === 0) {
return null;
}
return (
<div className="space-y-2">
{/* 输入标签 - 靠左 */}
<div className="text-xs text-muted-foreground font-medium"></div>
{/* 输入字段标签 */}
<div className="flex flex-wrap gap-1.5">
{allInputs.map((input, index) => (
<code
key={index}
className="text-xs px-2 py-1 bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 rounded-md font-mono inline-flex items-center"
title={input.description}
>
{input.title}
</code>
))}
</div>
</div>
);
};
// 渲染输出字段标签 // 渲染输出字段标签
const renderOutputSection = () => { const renderOutputSection = () => {
if (!nodeData.outputs || nodeData.outputs.length === 0) { if (!nodeData.outputs || nodeData.outputs.length === 0) {
@ -32,9 +79,9 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
<code <code
key={index} key={index}
className="text-xs px-2 py-1 bg-secondary text-secondary-foreground rounded-md font-mono inline-flex items-center" className="text-xs px-2 py-1 bg-secondary text-secondary-foreground rounded-md font-mono inline-flex items-center"
title={output.title || output.description} title={output.description}
> >
{output.name} {output.title || output.name}
</code> </code>
))} ))}
</div> </div>
@ -150,9 +197,13 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
</div> </div>
</CardHeader> </CardHeader>
{/* 输入/输出部分 */}
{(renderInputSection() || renderOutputSection()) && (
<CardContent className="pt-0 space-y-3">
{/* 输入部分 */}
{renderInputSection()}
{/* 输出部分 */} {/* 输出部分 */}
{renderOutputSection() && (
<CardContent className="pt-0">
{renderOutputSection()} {renderOutputSection()}
</CardContent> </CardContent>
)} )}

View File

@ -7,7 +7,8 @@ export enum DataSourceType {
JENKINS_SERVERS = 'JENKINS_SERVERS', JENKINS_SERVERS = 'JENKINS_SERVERS',
K8S_CLUSTERS = 'K8S_CLUSTERS', K8S_CLUSTERS = 'K8S_CLUSTERS',
GIT_REPOSITORIES = 'GIT_REPOSITORIES', GIT_REPOSITORIES = 'GIT_REPOSITORIES',
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES' DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES'
} }
/** /**
@ -44,6 +45,15 @@ export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
})); }));
} }
}, },
[DataSourceType.NOTIFICATION_CHANNEL_TYPES]: {
url: '/api/v1/notification-channel/types',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `${item.label}`,
value: item.code
}));
}
},
[DataSourceType.K8S_CLUSTERS]: { [DataSourceType.K8S_CLUSTERS]: {
url: '/api/v1/k8s-cluster/list', url: '/api/v1/k8s-cluster/list',
params: { enabled: true }, params: { enabled: true },