append-node.jsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /**
  2. * Append 拼接节点组件
  3. * 用于拼接多个字符串片段
  4. *
  5. * 特点:
  6. * - 没有执行端口(纯数据节点)
  7. * - 左边:动态数量的输入端口(文本片段 + 变量引用)
  8. * - 右边:一个输出端口(拼接结果)
  9. * - 蓝色主题
  10. */
  11. import { useState, useEffect } from 'react';
  12. import { parseEchoValue } from '../echo/echo-node.js';
  13. import { NodeRenderer } from '../node-renderer.jsx';
  14. import './append-node.css';
  15. export function AppendNode(props) {
  16. const { node, connections, onPortMouseDown, onPortMouseUp } = props;
  17. const [segments, setSegments] = useState([]);
  18. console.log(`🎨 [AppendNode] 渲染 Append 节点:`, { id: node.id, x: node.x, y: node.y, value: node.data?.value });
  19. // 解析 value 获取片段
  20. useEffect(() => {
  21. const value = node.data?.value || '';
  22. const parsed = parseEchoValue(value);
  23. console.log(` 📊 [AppendNode useEffect] 更新 segments:`, parsed);
  24. setSegments(parsed);
  25. }, [node.data?.value]);
  26. // 检查端口是否已连接
  27. const isPortConnected = (portId) => {
  28. if (!connections || !Array.isArray(connections)) return false;
  29. return connections.some(conn =>
  30. (conn.target === node.id && conn.targetPort === portId) ||
  31. (conn.source === node.id && conn.sourcePort === portId)
  32. );
  33. };
  34. // 数据端口
  35. const inputPorts = node.inputs || [];
  36. const outputPort = node.outputs?.[0];
  37. console.log(` 🔌 [AppendNode] 端口信息 - inputPorts:`, inputPorts.length, `outputPort:`, !!outputPort);
  38. // 自定义内容渲染
  39. const renderCustomBody = () => {
  40. console.log(` 🎨 [AppendNode renderCustomBody] 开始渲染,segments 数量: ${segments.length}, inputPorts 数量: ${inputPorts.length}`);
  41. return (
  42. <div className="Blueprint-append-node-content">
  43. {/* 左侧:输入片段 */}
  44. <div className="Blueprint-append-inputs">
  45. {segments.map((segment, index) => {
  46. const port = inputPorts.find(p => p.segmentIndex === index);
  47. console.log(` 🔍 渲染 segment[${index}]:`, segment, `找到的 port:`, port);
  48. if (!port) {
  49. console.log(` ⚠️ segment[${index}] 没有对应的 port,跳过渲染`);
  50. return null;
  51. }
  52. const connected = isPortConnected(port.id);
  53. const portClass = 'port-string';
  54. // 文本片段:空心圆 + 输入框(既可输入也可连接)
  55. if (segment.type === 'text') {
  56. console.log(` ✅ 渲染 segment[${index}] (text) - 空心圆 + 输入框`);
  57. return (
  58. <div key={index} className="Blueprint-append-segment">
  59. <div className="Blueprint-append-text-segment">
  60. <div
  61. className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
  62. data-port-type="data"
  63. onMouseDown={(e) => {
  64. e.stopPropagation();
  65. onPortMouseDown?.(node.id, port.id);
  66. }}
  67. onMouseUp={(e) => {
  68. e.stopPropagation();
  69. onPortMouseUp?.(node.id, port.id);
  70. }}
  71. title="文本输入(可连接变量)"
  72. >
  73. <div className="Blueprint-node-port-dot hollow"></div>
  74. </div>
  75. <input
  76. type="text"
  77. className="Blueprint-append-text-input"
  78. defaultValue={segment.content}
  79. placeholder="文本"
  80. onClick={(e) => e.stopPropagation()}
  81. onMouseDown={(e) => e.stopPropagation()}
  82. onChange={(e) => {
  83. // TODO: 更新 value 字段
  84. const newValue = e.target.value;
  85. // 需要重新组合整个 value
  86. }}
  87. />
  88. </div>
  89. </div>
  90. );
  91. }
  92. // 变量引用:实心圆 + 标签(专门连接变量)
  93. if (segment.type === 'variable') {
  94. console.log(` ✅ 渲染 segment[${index}] (variable) - 实心圆 + 标签`);
  95. return (
  96. <div key={index} className="Blueprint-append-segment">
  97. <div className="Blueprint-append-variable-segment">
  98. <div
  99. className={`Blueprint-node-port input ${portClass} ${connected ? 'connected' : 'disconnected'}`}
  100. data-port-type="data"
  101. onMouseDown={(e) => {
  102. e.stopPropagation();
  103. onPortMouseDown?.(node.id, port.id);
  104. }}
  105. onMouseUp={(e) => {
  106. e.stopPropagation();
  107. onPortMouseUp?.(node.id, port.id);
  108. }}
  109. title={`变量: ${segment.content}`}
  110. >
  111. <div className="Blueprint-node-port-dot solid"></div>
  112. <span className="Blueprint-node-port-label">{segment.content}</span>
  113. </div>
  114. </div>
  115. </div>
  116. );
  117. }
  118. return null;
  119. })}
  120. </div>
  121. {/* 右侧:输出端口 */}
  122. {outputPort && (
  123. <div className="Blueprint-append-output">
  124. <div
  125. className={`Blueprint-node-port output port-string ${isPortConnected(outputPort.id) ? 'connected' : 'disconnected'}`}
  126. data-port-type="data"
  127. onMouseDown={(e) => {
  128. e.stopPropagation();
  129. onPortMouseDown?.(node.id, outputPort.id);
  130. }}
  131. onMouseUp={(e) => {
  132. e.stopPropagation();
  133. onPortMouseUp?.(node.id, outputPort.id);
  134. }}
  135. title="拼接结果"
  136. >
  137. <span className="Blueprint-node-port-label">Result</span>
  138. <div className="Blueprint-node-port-dot solid"></div>
  139. </div>
  140. </div>
  141. )}
  142. </div>
  143. );
  144. };
  145. return (
  146. <NodeRenderer
  147. {...props}
  148. className="Blueprint-append-node"
  149. headerText="Append"
  150. layoutType="append-custom"
  151. renderCustomBody={renderCustomBody}
  152. />
  153. );
  154. }