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 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(
@ -133,8 +90,6 @@ const VariableInput: React.FC<VariableInputProps> = ({
nodeName, nodeName,
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()}

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: { 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: '🔔',
@ -29,7 +29,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
}, },
handles: { handles: {
input: true, input: true,
output: true output: true
}, },
features: { features: {
showBadge: true, showBadge: true,
@ -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", type: "string",
title: "输入映射", enum: ["SUCCESS", "FAILURE"],
description: "配置从上游节点获取的数据", description: "执行的结果状态",
properties: { example: "SUCCESS",
dynamicTitle: { required: true
type: "string", }]
title: "动态标题",
description: "使用上游节点输出动态设置标题,如 ${upstream.buildStatus}",
default: ""
},
dynamicContent: {
type: "string",
title: "动态内容",
description: "使用上游节点输出动态设置内容,如 构建结果: ${upstream.buildStatus}",
format: "textarea",
default: ""
},
dynamicRecipients: {
type: "string",
title: "动态收件人",
description: "使用上游节点输出动态设置收件人",
default: ""
}
}
}
// ✅ 通知节点没有输出能力(不定义 outputs 字段)
}; };