1
This commit is contained in:
parent
313307a19d
commit
80ad106934
@ -1,47 +1,10 @@
|
|||||||
import React, { useState, useRef, useMemo, useEffect } from 'react';
|
import React, { useState, useRef, useMemo, useEffect } from 'react';
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/components/ui/command';
|
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import type { VariableInputProps } from './types';
|
import type { VariableInputProps } from './types';
|
||||||
import { detectTrigger, insertVariable, filterVariables } from './utils';
|
import { detectTrigger, insertVariable, filterVariables } from './utils';
|
||||||
import { collectNodeVariables, groupVariablesByNode } from '@/utils/workflow/collectNodeVariables';
|
import { collectNodeVariables, groupVariablesByNode } from '@/utils/workflow/collectNodeVariables';
|
||||||
|
|
||||||
/**
|
|
||||||
* 高亮渲染 ${} 变量
|
|
||||||
*/
|
|
||||||
const highlightVariables = (text: string): React.ReactNode[] => {
|
|
||||||
const regex = /\$\{([^}]+)\}/g;
|
|
||||||
const parts: React.ReactNode[] = [];
|
|
||||||
let lastIndex = 0;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(text)) !== null) {
|
|
||||||
// 添加变量前的文本
|
|
||||||
if (match.index > lastIndex) {
|
|
||||||
parts.push(text.substring(lastIndex, match.index));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加高亮的变量
|
|
||||||
parts.push(
|
|
||||||
<span
|
|
||||||
key={match.index}
|
|
||||||
className="inline-block px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded font-mono text-sm border border-blue-200 dark:border-blue-800"
|
|
||||||
>
|
|
||||||
${`{${match[1]}}`}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
lastIndex = match.index + match[0].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加剩余文本
|
|
||||||
if (lastIndex < text.length) {
|
|
||||||
parts.push(text.substring(lastIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.length > 0 ? parts : [text];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 变量输入组件
|
* 变量输入组件
|
||||||
* 支持在输入 ${ 时自动提示可用变量
|
* 支持在输入 ${ 时自动提示可用变量
|
||||||
@ -115,15 +78,9 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
|
|
||||||
// 插入变量
|
// 插入变量
|
||||||
const handleSelectVariable = (nodeName: string, fieldName: string) => {
|
const handleSelectVariable = (nodeName: string, fieldName: string) => {
|
||||||
console.log('🎯 handleSelectVariable 被调用:', { nodeName, fieldName, value, triggerInfo });
|
if (!inputRef.current) return;
|
||||||
|
|
||||||
if (!inputRef.current) {
|
|
||||||
console.error('❌ inputRef.current 不存在');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cursorPos = inputRef.current.selectionStart || 0;
|
const cursorPos = inputRef.current.selectionStart || 0;
|
||||||
console.log('📍 当前光标位置:', cursorPos);
|
|
||||||
|
|
||||||
// 使用工具函数插入变量
|
// 使用工具函数插入变量
|
||||||
const { newText, newCursorPos } = insertVariable(
|
const { newText, newCursorPos } = insertVariable(
|
||||||
@ -134,8 +91,6 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
fieldName
|
fieldName
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ 插入结果:', { newText, newCursorPos });
|
|
||||||
|
|
||||||
// 更新值
|
// 更新值
|
||||||
onChange(newText);
|
onChange(newText);
|
||||||
|
|
||||||
@ -147,7 +102,6 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
|
inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
|
||||||
console.log('✅ 焦点已恢复,光标已设置');
|
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@ -200,10 +154,7 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
<div
|
<div
|
||||||
key={`${variable.nodeId}.${variable.fieldName}`}
|
key={`${variable.nodeId}.${variable.fieldName}`}
|
||||||
className="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-accent rounded-sm transition-colors"
|
className="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-accent rounded-sm transition-colors"
|
||||||
onClick={() => {
|
onClick={() => handleSelectVariable(variable.nodeName, variable.fieldName)}
|
||||||
console.log('点击变量:', variable.nodeName, variable.fieldName);
|
|
||||||
handleSelectVariable(variable.nodeName, variable.fieldName);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span className="font-mono text-sm">{variable.fieldName}</span>
|
<span className="font-mono text-sm">{variable.fieldName}</span>
|
||||||
<span className="ml-auto text-xs text-muted-foreground">{variable.fieldType}</span>
|
<span className="ml-auto text-xs text-muted-foreground">{variable.fieldType}</span>
|
||||||
@ -216,28 +167,20 @@ const VariableInput: React.FC<VariableInputProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检测是否有变量需要高亮
|
|
||||||
const hasVariables = value && /\$\{[^}]+\}/.test(value);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative space-y-2">
|
<div ref={containerRef} className="relative">
|
||||||
{/* 输入框 */}
|
{/* 输入框 */}
|
||||||
{renderInput()}
|
{renderInput()}
|
||||||
|
|
||||||
{/* 变量高亮预览 */}
|
{/* 变量提示弹窗 - 绝对定位,使用更高的 z-index */}
|
||||||
{hasVariables && !showSuggestions && (
|
|
||||||
<div className="p-2 bg-muted/30 rounded-md border border-border text-sm">
|
|
||||||
<div className="text-xs text-muted-foreground mb-1">变量预览:</div>
|
|
||||||
<div className="break-words leading-relaxed">
|
|
||||||
{highlightVariables(value)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 变量提示弹窗 - 绝对定位 */}
|
|
||||||
{showSuggestions && (
|
{showSuggestions && (
|
||||||
<div
|
<div
|
||||||
className="absolute top-full left-0 w-full mt-2 z-50"
|
className="fixed mt-2 z-[9999]"
|
||||||
|
style={{
|
||||||
|
top: inputRef.current ? inputRef.current.getBoundingClientRect().bottom + window.scrollY + 8 : 0,
|
||||||
|
left: inputRef.current ? inputRef.current.getBoundingClientRect().left + window.scrollX : 0,
|
||||||
|
width: inputRef.current ? inputRef.current.getBoundingClientRect().width : 'auto',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="w-full max-w-[500px] bg-popover text-popover-foreground rounded-md border shadow-lg">
|
<div className="w-full max-w-[500px] bg-popover text-popover-foreground rounded-md border shadow-lg">
|
||||||
{renderSuggestions()}
|
{renderSuggestions()}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from './types';
|
import {ConfigurableNodeDefinition, NodeType, NodeCategory} from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知节点定义
|
* 通知节点定义
|
||||||
@ -14,7 +14,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
// 渲染配置
|
// 渲染配置
|
||||||
renderConfig: {
|
renderConfig: {
|
||||||
shape: 'rounded-rect',
|
shape: 'rounded-rect',
|
||||||
size: { width: 120, height: 60 },
|
size: {width: 120, height: 60},
|
||||||
icon: {
|
icon: {
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
content: '🔔',
|
content: '🔔',
|
||||||
@ -85,35 +85,14 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
},
|
},
|
||||||
required: ["nodeName", "nodeCode", "notificationType", "title", "content", "recipients"]
|
required: ["nodeName", "nodeCode", "notificationType", "title", "content", "recipients"]
|
||||||
},
|
},
|
||||||
|
outputs: [{
|
||||||
// 输入映射 Schema(可以引用上游节点的输出)
|
name: "status",
|
||||||
inputMappingSchema: {
|
title: "执行状态",
|
||||||
type: "object",
|
|
||||||
title: "输入映射",
|
|
||||||
description: "配置从上游节点获取的数据",
|
|
||||||
properties: {
|
|
||||||
dynamicTitle: {
|
|
||||||
type: "string",
|
type: "string",
|
||||||
title: "动态标题",
|
enum: ["SUCCESS", "FAILURE"],
|
||||||
description: "使用上游节点输出动态设置标题,如 ${upstream.buildStatus}",
|
description: "执行的结果状态",
|
||||||
default: ""
|
example: "SUCCESS",
|
||||||
},
|
required: true
|
||||||
dynamicContent: {
|
}]
|
||||||
type: "string",
|
|
||||||
title: "动态内容",
|
|
||||||
description: "使用上游节点输出动态设置内容,如 构建结果: ${upstream.buildStatus}",
|
|
||||||
format: "textarea",
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
dynamicRecipients: {
|
|
||||||
type: "string",
|
|
||||||
title: "动态收件人",
|
|
||||||
description: "使用上游节点输出动态设置收件人",
|
|
||||||
default: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 通知节点没有输出能力(不定义 outputs 字段)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user