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)',
textDecorationThickness: '2px',
textUnderlineOffset: '2px',
// 确保垂直对齐
display: 'inline',
verticalAlign: 'baseline',
}}
>
{match[0]}

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types';
import {DataSourceType} from "@/pages/Workflow/Design/utils/dataSourceLoader.ts";
/**
*
@ -60,14 +61,28 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
title: "节点描述",
description: "节点的详细说明",
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: {
type: "string",
title: "通知类型",
description: "选择通知发送的渠道",
enum: ["EMAIL", "DINGTALK", "WECHAT_WORK", "SMS"],
enumNames: ["邮件", "钉钉", "企业微信", "短信"],
default: "EMAIL"
'x-dataSource': DataSourceType.NOTIFICATION_CHANNEL_TYPES
},
title: {
type: "string",
@ -83,7 +98,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
default: ""
}
},
required: ["nodeName", "nodeCode", "notificationType", "title", "content"]
required: ["jenkinsServerId"]
},
outputs: [{
name: "status",

View File

@ -2,6 +2,7 @@ import React from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
import type { FlowNodeData } from '../../types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { isConfigurableNode } from '../types';
/**
* BaseNode - shadcn/ui
@ -17,6 +18,52 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
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 = () => {
if (!nodeData.outputs || nodeData.outputs.length === 0) {
@ -32,9 +79,9 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
<code
key={index}
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>
))}
</div>
@ -150,9 +197,13 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
</div>
</CardHeader>
{/* 输出部分 */}
{renderOutputSection() && (
<CardContent className="pt-0">
{/* 输入/输出部分 */}
{(renderInputSection() || renderOutputSection()) && (
<CardContent className="pt-0 space-y-3">
{/* 输入部分 */}
{renderInputSection()}
{/* 输出部分 */}
{renderOutputSection()}
</CardContent>
)}

View File

@ -7,7 +7,8 @@ export enum DataSourceType {
JENKINS_SERVERS = 'JENKINS_SERVERS',
K8S_CLUSTERS = 'K8S_CLUSTERS',
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]: {
url: '/api/v1/k8s-cluster/list',
params: { enabled: true },