This commit is contained in:
dengqichen 2025-10-22 13:51:55 +08:00
parent 313307a19d
commit 80ad106934
2 changed files with 22 additions and 100 deletions

View File

@ -1,47 +1,10 @@
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 { Textarea } from '@/components/ui/textarea';
import type { VariableInputProps } from './types';
import { detectTrigger, insertVariable, filterVariables } from './utils';
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) => {
console.log('🎯 handleSelectVariable 被调用:', { nodeName, fieldName, value, triggerInfo });
if (!inputRef.current) {
console.error('❌ inputRef.current 不存在');
return;
}
if (!inputRef.current) return;
const cursorPos = inputRef.current.selectionStart || 0;
console.log('📍 当前光标位置:', cursorPos);
// 使用工具函数插入变量
const { newText, newCursorPos } = insertVariable(
@ -134,8 +91,6 @@ const VariableInput: React.FC<VariableInputProps> = ({
fieldName
);
console.log('✅ 插入结果:', { newText, newCursorPos });
// 更新值
onChange(newText);
@ -147,7 +102,6 @@ const VariableInput: React.FC<VariableInputProps> = ({
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
console.log('✅ 焦点已恢复,光标已设置');
}
}, 0);
};
@ -200,10 +154,7 @@ const VariableInput: React.FC<VariableInputProps> = ({
<div
key={`${variable.nodeId}.${variable.fieldName}`}
className="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-accent rounded-sm transition-colors"
onClick={() => {
console.log('点击变量:', variable.nodeName, variable.fieldName);
handleSelectVariable(variable.nodeName, variable.fieldName);
}}
onClick={() => handleSelectVariable(variable.nodeName, variable.fieldName)}
>
<span className="font-mono text-sm">{variable.fieldName}</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 (
<div ref={containerRef} className="relative space-y-2">
<div ref={containerRef} className="relative">
{/* 输入框 */}
{renderInput()}
{/* 变量高亮预览 */}
{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>
)}
{/* 变量提示弹窗 - 绝对定位 */}
{/* 变量提示弹窗 - 绝对定位,使用更高的 z-index */}
{showSuggestions && (
<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">
{renderSuggestions()}

View File

@ -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: {
shape: 'rounded-rect',
size: { width: 120, height: 60 },
size: {width: 120, height: 60},
icon: {
type: 'emoji',
content: '🔔',
@ -85,35 +85,14 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
},
required: ["nodeName", "nodeCode", "notificationType", "title", "content", "recipients"]
},
// 输入映射 Schema可以引用上游节点的输出
inputMappingSchema: {
type: "object",
title: "输入映射",
description: "配置从上游节点获取的数据",
properties: {
dynamicTitle: {
outputs: [{
name: "status",
title: "执行状态",
type: "string",
title: "动态标题",
description: "使用上游节点输出动态设置标题,如 ${upstream.buildStatus}",
default: ""
},
dynamicContent: {
type: "string",
title: "动态内容",
description: "使用上游节点输出动态设置内容,如 构建结果: ${upstream.buildStatus}",
format: "textarea",
default: ""
},
dynamicRecipients: {
type: "string",
title: "动态收件人",
description: "使用上游节点输出动态设置收件人",
default: ""
}
}
}
// ✅ 通知节点没有输出能力(不定义 outputs 字段)
enum: ["SUCCESS", "FAILURE"],
description: "执行的结果状态",
example: "SUCCESS",
required: true
}]
};