FlowNode.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. using Prism.Mvvm;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7. using System.Diagnostics.Tracing;
  8. using System.Linq;
  9. using System.Runtime.Serialization;
  10. using System.Threading;
  11. using System.Windows;
  12. using System.Windows.Media;
  13. using Newtonsoft.Json;
  14. using TeamAAS.FlowEditor.Execution;
  15. using TeamAAS.FlowEditor.Plugins;
  16. namespace TeamAAS.FlowEditor.Models
  17. {
  18. [Serializable]
  19. /// <summary>
  20. /// 流程节点基类 - 所有节点类型的公共属性
  21. /// </summary>
  22. public class FlowNode : TeamAAS.BindableBase
  23. {
  24. #region 标识属性
  25. [Category("II.杂项")]
  26. [DisplayName("1.节点ID")]
  27. [ReadOnly(true)]
  28. [Browsable(true)]
  29. public string NodeId { get; set; } = System.Guid.NewGuid().ToString("N");
  30. private string _nodeName = "新节点";
  31. [Category("I.节点参数")]
  32. [DisplayName("1.节点名称")]
  33. [Description("设置流程名称")]
  34. [Browsable(true)]
  35. /// <summary>
  36. /// 节点唯一ID(用于连接线关联,自动生成)
  37. /// </summary>
  38. public string NodeName
  39. {
  40. get => _nodeName;
  41. set
  42. {
  43. if (SetProperty(ref _nodeName, value))
  44. {
  45. if (PluginModel?.GetModel != null)
  46. PluginModel.GetModel.NodeName = value;
  47. }
  48. }
  49. }
  50. [Category("II.杂项")]
  51. [DisplayName("2.工具ID")]
  52. [ReadOnly(true)]
  53. [Browsable(true)]
  54. /// <summary>
  55. /// 关联的插件ID(兼容旧代码) 不变
  56. /// </summary>
  57. public string PluginId { get; set; }
  58. /// <summary>
  59. /// 所属流程绑定的结果注册表(运行态=Main,编辑/调试态=Debug)。
  60. /// 由 FlowGraph/InitializeNode 注入,并同步到 PluginModel.Registry。
  61. /// </summary>
  62. [JsonIgnore]
  63. [ReadOnly(true)]
  64. [Browsable(false)]
  65. public ResultRegistry Registry
  66. {
  67. get => _registry;
  68. set
  69. {
  70. _registry = value;
  71. if (PluginModel != null)
  72. PluginModel.Registry = value;
  73. }
  74. }
  75. [NonSerialized]
  76. private ResultRegistry _registry;
  77. [Category("II.杂项")]
  78. [DisplayName("3.工具名称")]
  79. [Description("设置工具名称")]
  80. [Browsable(true)]
  81. [ReadOnly(true)]
  82. /// <summary>
  83. /// 工具名称
  84. /// </summary>
  85. public string ToolName
  86. {
  87. get => PluginModel?.GetModel?.ToolName;
  88. set
  89. {
  90. if (PluginModel?.GetModel != null)
  91. PluginModel.GetModel.ToolName = value;
  92. }
  93. }
  94. private string _flowId;
  95. [Category("II.杂项")]
  96. [DisplayName("4.流程ID")]
  97. [ReadOnly(true)]
  98. [Browsable(true)]
  99. /// <summary>
  100. /// 所属流程ID(用于结果注册表和公式解析)
  101. /// </summary>
  102. public string FlowId
  103. {
  104. get => _flowId;
  105. set
  106. {
  107. if (_flowId != value)
  108. {
  109. _flowId = value;
  110. if (PluginModel?.GetModel != null)
  111. PluginModel.GetModel.FlowId = value;
  112. }
  113. }
  114. }
  115. private string _flowName;
  116. [Category("II.杂项")]
  117. [DisplayName("5.流程名称")]
  118. [ReadOnly(true)]
  119. [Browsable(true)]
  120. /// <summary>
  121. /// 所属流程名称(用于UI展示和公式解析)
  122. /// </summary>
  123. public string FlowName
  124. {
  125. get => _flowName;
  126. set
  127. {
  128. if (_flowName != value)
  129. {
  130. _flowName = value;
  131. if (PluginModel?.GetModel != null)
  132. PluginModel.GetModel.FlowName = value;
  133. }
  134. }
  135. }
  136. /// <summary>节点是否启用(委托到 PluginModel.IsEnable,禁用后不执行+画布变灰)</summary>
  137. [Category("I.节点参数")]
  138. [DisplayName("2.启用工具")]
  139. [Description("是否启用工具")]
  140. [Browsable(true)]
  141. public bool IsEnabled
  142. {
  143. get => PluginModel?.IsEnable ?? true;
  144. set
  145. {
  146. if (PluginModel != null && PluginModel.IsEnable != value)
  147. {
  148. PluginModel.IsEnable = value;
  149. RaisePropertyChanged(nameof(IsEnabled));
  150. }
  151. }
  152. }
  153. [Browsable(false)]
  154. /// <summary>
  155. /// 节点分类(用于UI展示不同形状和颜色)
  156. /// </summary>
  157. public NodeCategory Category { get; set; } = NodeCategory.Normal;
  158. private IFlowNodePlugin _PluginModel { get; set; }
  159. [Browsable(false)]
  160. /// <summary>
  161. /// 插件模型数据(序列化存储,类型为 BasePluginModel 派生类)
  162. /// </summary>
  163. public IFlowNodePlugin PluginModel
  164. {
  165. get => _PluginModel;
  166. set
  167. {
  168. if (value != null && _PluginModel != value)
  169. {
  170. _PluginModel = value;
  171. //_PluginModel.InitRun();
  172. // 同步 FlowNode 当前属性到模型
  173. _PluginModel.Registry = _registry;
  174. _PluginModel.GetModel.NodeName = _nodeName;
  175. if (_flowName != null) _PluginModel.GetModel.FlowName = _flowName;
  176. if (_flowId != null) _PluginModel.GetModel.FlowId = _flowId;
  177. SetResults(FlowId, _PluginModel.LastResults);
  178. }
  179. }
  180. }
  181. [Category("I.节点参数")]
  182. [DisplayName("3.跳过警告")]
  183. [Description("是否跳过返回值为 Warning 的返回任务")]
  184. [Browsable(true)]
  185. public bool IsSkipWarning
  186. {
  187. get; set;
  188. } = false;
  189. private int _preDelayMs = 0;
  190. [Category("I.节点参数")]
  191. [DisplayName("4.运行前休眠(ms)")]
  192. [Description("节点执行前等待的毫秒数。默认0=不等待,最小0ms。")]
  193. [Browsable(true)]
  194. public int PreDelayMs
  195. {
  196. get => _preDelayMs;
  197. set => _preDelayMs = value <= 0 ? 0 : value;
  198. }
  199. private int _postDelayMs = 0;
  200. [Category("I.节点参数")]
  201. [DisplayName("5.运行后休眠(ms)")]
  202. [Description("节点执行完成后等待的毫秒数,再启动后继节点。默认0=不等待,最小0ms。")]
  203. [Browsable(true)]
  204. public int PostDelayMs
  205. {
  206. get => _postDelayMs;
  207. set => _postDelayMs = value <= 0 ? 0 : value;
  208. }
  209. private int _timeoutMs = 0;
  210. [Category("I.节点参数")]
  211. [DisplayName("运行超时(ms)")]
  212. [Description("节点执行超时看门狗:超过该毫秒数仍未返回则判定为超时失败并继续后续流程(防止硬件卡死拖垮整条流程)。默认0=不限时。注:阻塞式硬件调用无法被强制中断,超时仅保证流程不再无限等待。")]
  213. [Browsable(true)]
  214. public int TimeoutMs
  215. {
  216. get => _timeoutMs;
  217. set => _timeoutMs = value <= 0 ? 0 : value;
  218. }
  219. /// <summary>
  220. /// 任务插件「日志存储详细度」(委托到 PluginModel.GetModel.StoreVerbosity,实际存储与序列化在模型侧)。
  221. /// 粗略=只写 L1/L2 到公共日志;详细=额外把 L3/L4 写到独立路径。
  222. /// </summary>
  223. [Category("I.节点参数")]
  224. [DisplayName("6.日志存储")]
  225. [Description("日志存储详细度:粗略=只写公共日志(L1/L2);详细=额外把 L3/L4 写到 流程\\任务 独立日志")]
  226. [Browsable(true)]
  227. public TeamAAS.Logging.LogVerbosity StoreVerbosity
  228. {
  229. get => PluginModel?.GetModel?.StoreVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略;
  230. set
  231. {
  232. if (PluginModel?.GetModel != null && PluginModel.GetModel.StoreVerbosity != value)
  233. {
  234. PluginModel.GetModel.StoreVerbosity = value;
  235. RaisePropertyChanged(nameof(StoreVerbosity));
  236. }
  237. }
  238. }
  239. /// <summary>
  240. /// 任务插件「日志显示详细度」(委托到 PluginModel.GetModel.DisplayVerbosity,实际存储在模型侧)。
  241. /// 粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(与是否落盘无关)。
  242. /// </summary>
  243. [Category("I.节点参数")]
  244. [DisplayName("7.日志显示")]
  245. [Description("日志显示详细度:粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(独立于存储)")]
  246. [Browsable(true)]
  247. public TeamAAS.Logging.LogVerbosity DisplayVerbosity
  248. {
  249. get => PluginModel?.GetModel?.DisplayVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略;
  250. set
  251. {
  252. if (PluginModel?.GetModel != null && PluginModel.GetModel.DisplayVerbosity != value)
  253. {
  254. PluginModel.GetModel.DisplayVerbosity = value;
  255. RaisePropertyChanged(nameof(DisplayVerbosity));
  256. }
  257. }
  258. }
  259. #endregion
  260. #region 布局属性
  261. [Browsable(false)]
  262. public double X
  263. {
  264. get => _x;
  265. set => SetProperty(ref _x, value);
  266. }
  267. private double _x = 100;
  268. [Browsable(false)]
  269. public double Y
  270. {
  271. get => _y;
  272. set => SetProperty(ref _y, value);
  273. }
  274. private double _y = 100;
  275. #endregion
  276. #region 运行属性
  277. [Browsable(false)]
  278. [JsonIgnore]
  279. public NodeRunStatus Status
  280. {
  281. get => _status;
  282. set => SetProperty(ref _status, value);
  283. }
  284. [NonSerialized]
  285. private NodeRunStatus _status = NodeRunStatus.NotStarted;
  286. [Browsable(false)]
  287. [JsonIgnore]
  288. public int CostTime
  289. {
  290. get => _costTime;
  291. set => SetProperty(ref _costTime, value);
  292. }
  293. [NonSerialized]
  294. private int _costTime;
  295. #endregion
  296. #region 异常分支触发状态(运行时,不序列化)
  297. [NonSerialized] private int _exceptionTriggers;
  298. [NonSerialized] private int _exceptionRunFlag;
  299. [Browsable(false)]
  300. [JsonIgnore]
  301. public int ExceptionTriggers => _exceptionTriggers;
  302. /// <summary>原子 +1 触发计数,返回新值。用于异常分支节点排队触发。</summary>
  303. public int IncrementExceptionTrigger() => Interlocked.Increment(ref _exceptionTriggers);
  304. /// <summary>原子 -1 触发计数,返回新值。每次实际执行完一次后调用。</summary>
  305. public int DecrementExceptionTrigger() => Interlocked.Decrement(ref _exceptionTriggers);
  306. /// <summary>尝试获取运行权:CAS 把 _exceptionRunFlag 从 0 改为 1。成功=true 表示获得运行权。</summary>
  307. public bool TryAcquireExceptionRun() =>
  308. Interlocked.CompareExchange(ref _exceptionRunFlag, 1, 0) == 0;
  309. /// <summary>释放运行权:把 _exceptionRunFlag 重置为 0。</summary>
  310. public void ReleaseExceptionRun() => Interlocked.Exchange(ref _exceptionRunFlag, 0);
  311. /// <summary>重置异常触发状态(每次流程开始 ResetAllNodes 时调用)。</summary>
  312. public void ResetExceptionState()
  313. {
  314. Interlocked.Exchange(ref _exceptionTriggers, 0);
  315. Interlocked.Exchange(ref _exceptionRunFlag, 0);
  316. }
  317. #endregion
  318. #region 执行结果
  319. [NonSerialized]
  320. private ObservableCollection<ResultItem> _resultItems = new ObservableCollection<ResultItem>();
  321. [Browsable(false)]
  322. [JsonIgnore]
  323. public ObservableCollection<ResultItem> ResultItems
  324. {
  325. get => _resultItems;
  326. set => SetProperty(ref _resultItems, value);
  327. }
  328. /// <summary>
  329. /// 执行历史记录(每次执行追加一条)
  330. /// </summary>
  331. [NonSerialized]
  332. private ObservableCollection<ExecutionHistoryEntry> _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
  333. [Browsable(false)]
  334. [JsonIgnore]
  335. public ObservableCollection<ExecutionHistoryEntry> ExecutionHistory
  336. {
  337. get => _executionHistory;
  338. set => SetProperty(ref _executionHistory, value);
  339. }
  340. /// <summary>
  341. /// 当前选中的历史记录(UI展示用)
  342. /// </summary>
  343. [NonSerialized]
  344. private ExecutionHistoryEntry _selectedHistoryEntry;
  345. [Browsable(false)]
  346. [JsonIgnore]
  347. public ExecutionHistoryEntry SelectedHistoryEntry
  348. {
  349. get => _selectedHistoryEntry;
  350. set => SetProperty(ref _selectedHistoryEntry, value);
  351. }
  352. [Browsable(false)]
  353. /// <summary>
  354. /// 历史记录最大保留条数(默认50,可外部设置)
  355. /// </summary>
  356. public int MaxHistoryCount { get; set; } = 50;
  357. /// <summary>
  358. /// 节点最新执行结果原始字典(供公式引用,不参与序列化)
  359. /// </summary>
  360. [Browsable(false)]
  361. [JsonIgnore]
  362. public Dictionary<string, object> RawResults { get; set; }
  363. private static bool IsBasicType(object value)
  364. {
  365. if (value == null) return true;
  366. var t = value.GetType();
  367. return t.IsPrimitive || t == typeof(string) || t == typeof(decimal) ||
  368. t == typeof(DateTime) || t == typeof(TimeSpan) || t == typeof(Guid) || t.IsEnum;
  369. }
  370. /// <summary>
  371. /// 设置执行结果(UI线程调用)
  372. /// </summary>
  373. public void SetResults(string GraphId, Dictionary<string, object> results)
  374. {
  375. if (_resultItems == null)
  376. _resultItems = new ObservableCollection<ResultItem>();
  377. _resultItems?.Clear();
  378. if (results != null)
  379. {
  380. foreach (var kv in results)
  381. {
  382. // 非基础类型只存 ToString,避免图像/CogRecord 等大对象挂在 UI 和历史快照里
  383. // RawResults 保留原始引用供节点间公式解析使用
  384. bool converted = kv.Value != null && !IsBasicType(kv.Value);
  385. _resultItems?.Add(new ResultItem
  386. {
  387. Key = kv.Key,
  388. Value = converted ? kv.Value.ToString() : kv.Value,
  389. // 转换后 TypeName 显式记录原始实际类型,避免显示成 String
  390. TypeName = converted ? ResultItem.DescribeType(kv.Value.GetType()) : null
  391. });
  392. }
  393. }
  394. RaisePropertyChanged(nameof(ResultItems));
  395. RaisePropertyChanged(nameof(ExecutionHistory));
  396. RaisePropertyChanged(nameof(SelectedHistoryEntry));
  397. // 添加到历史记录
  398. var snapshot = new ObservableCollection<ResultItem>();
  399. foreach (var item in _resultItems)
  400. snapshot.Add(new ResultItem { Key = item.Key, Value = item.Value, TypeName = item.TypeName });
  401. var entry = new ExecutionHistoryEntry(DateTime.Now, snapshot);
  402. if (_executionHistory == null)
  403. _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
  404. Application.Current?.Dispatcher.Invoke(() => _executionHistory.Insert(0, entry));
  405. // 超过最大条数自动清理最旧的(末尾)
  406. while (_executionHistory.Count > MaxHistoryCount)
  407. Application.Current?.Dispatcher.Invoke(() => _executionHistory.RemoveAt(_executionHistory.Count - 1));
  408. SelectedHistoryEntry = entry; // 自动选最新
  409. RawResults = results;
  410. }
  411. #endregion
  412. #region UI辅助属性
  413. [Browsable(false)]
  414. /// <summary>
  415. /// 节点图标(Material Design 图标 Kind 名,如 "Camera";由 MahApps IconPacks 渲染)
  416. /// </summary>
  417. public string IconGeometry { get; set; } = "CircleOutline";
  418. [Browsable(false)]
  419. [JsonIgnore]
  420. public bool IsSelected
  421. {
  422. get => _isSelected;
  423. set
  424. {
  425. if (SetProperty(ref _isSelected, value))
  426. RaisePropertyChanged(nameof(BorderColor));
  427. }
  428. }
  429. [NonSerialized]
  430. private bool _isSelected;
  431. [Browsable(false)]
  432. [JsonIgnore]
  433. /// <summary>
  434. /// 边框颜色(选中时高亮)
  435. /// </summary>
  436. public string BorderColor => IsSelected ? "#007ACC" : "#3F3F46";
  437. /// <summary>
  438. /// 节点宽度(可被实际渲染尺寸覆盖)
  439. /// </summary>
  440. [NonSerialized]
  441. private double _nodeWidth = 0;
  442. [Browsable(false)]
  443. public double NodeWidth
  444. {
  445. get
  446. {
  447. if (_nodeWidth > 0) return _nodeWidth;
  448. switch (Category)
  449. {
  450. case NodeCategory.Decision: return 180;
  451. case NodeCategory.ForLoop: return 180;
  452. case NodeCategory.Group: return 200;
  453. case NodeCategory.End: return 140;
  454. case NodeCategory.ExceptionBranch: return 160;
  455. default: return 160;
  456. }
  457. }
  458. set => _nodeWidth = value;
  459. }
  460. /// <summary>
  461. /// 节点高度(可被实际渲染尺寸覆盖)
  462. /// </summary>
  463. [NonSerialized]
  464. private double _nodeHeight = 0;
  465. [Browsable(false)]
  466. public double NodeHeight
  467. {
  468. get
  469. {
  470. if (_nodeHeight > 0) return _nodeHeight;
  471. switch (Category)
  472. {
  473. case NodeCategory.Decision: return 66;
  474. case NodeCategory.ForLoop: return 66;
  475. case NodeCategory.Group: return 66;
  476. case NodeCategory.End: return 52;
  477. case NodeCategory.ExceptionBranch: return 66;
  478. default: return 66;
  479. }
  480. }
  481. set => _nodeHeight = value;
  482. }
  483. [Browsable(false)]
  484. [JsonIgnore]
  485. /// <summary>
  486. /// 是否有第二个输出端口(判断节点)
  487. /// </summary>
  488. public System.Windows.Visibility HasSecondOutput
  489. {
  490. get
  491. {
  492. return Category == NodeCategory.Decision
  493. ? System.Windows.Visibility.Visible
  494. : System.Windows.Visibility.Collapsed;
  495. }
  496. }
  497. [Browsable(false)]
  498. [JsonIgnore]
  499. /// <summary>
  500. /// 分类对应的标题色(十六进制)
  501. /// </summary>
  502. public string CategoryColor
  503. {
  504. get
  505. {
  506. switch (Category)
  507. {
  508. case NodeCategory.Decision: return "#C2771A";
  509. case NodeCategory.ToolBlock: return "#7B1FA2";
  510. case NodeCategory.ForLoop: return "#00897B";
  511. case NodeCategory.Group: return "#E65100";
  512. case NodeCategory.End: return "#D32F2F";
  513. case NodeCategory.ExceptionBranch: return "#B71C1C";
  514. default: return "#007ACC";
  515. }
  516. }
  517. }
  518. [Browsable(false)]
  519. [JsonIgnore]
  520. /// <summary>
  521. /// 状态文本(属性面板用)
  522. /// </summary>
  523. public string StatusText
  524. {
  525. get
  526. {
  527. switch (Status)
  528. {
  529. case NodeRunStatus.NotStarted: return "未运行";
  530. case NodeRunStatus.Running: return "运行中...";
  531. case NodeRunStatus.Success: return "成功";
  532. case NodeRunStatus.Failed: return "失败";
  533. case NodeRunStatus.Skipped: return "跳过";
  534. default: return "";
  535. }
  536. }
  537. }
  538. [Browsable(false)]
  539. [JsonIgnore]
  540. /// <summary>
  541. /// 耗时显示文本(节点上显示)
  542. /// </summary>
  543. public string CostTimeText
  544. {
  545. get
  546. {
  547. if (CostTime == 0) return "0ms";
  548. if (CostTime < 1000) return CostTime + "ms";
  549. return (CostTime / 1000.0).ToString("F1") + "s";
  550. }
  551. }
  552. [Browsable(false)]
  553. [JsonIgnore]
  554. /// <summary>
  555. /// 状态指示色(十六进制)
  556. /// </summary>
  557. public string StatusColor
  558. {
  559. get
  560. {
  561. switch (Status)
  562. {
  563. case NodeRunStatus.NotStarted: return "#888888";
  564. case NodeRunStatus.Running: return "#FF9800";
  565. case NodeRunStatus.Success: return "#4CAF50";
  566. case NodeRunStatus.Failed: return "#F44336";
  567. case NodeRunStatus.Skipped: return "#666666";
  568. default: return "#888888";
  569. }
  570. }
  571. }
  572. public void NotifyStatusChanged()
  573. {
  574. RaisePropertyChanged(nameof(StatusText));
  575. RaisePropertyChanged(nameof(StatusColor));
  576. RaisePropertyChanged(nameof(CostTimeText));
  577. //RaisePropertyChanged(nameof(PropertySummary));
  578. RaisePropertyChanged(nameof(ResultItems));
  579. }
  580. #endregion
  581. public FlowNode()
  582. {
  583. }
  584. /// <summary>
  585. /// 初始化ID链接。
  586. /// 导入 .aas / 打开产品流程 / 复制粘贴等所有加载路径最终都会经过这里,
  587. /// 是节点"重挂接"的唯一入口;插件初始化(InitPlugin + 注册默认输出)也在这里统一补跑。
  588. /// </summary>
  589. /// <param name="flowId"></param>
  590. /// <param name="flowName"></param>
  591. public void InitializeNode(string flowId, string flowName, ResultRegistry registry = null)
  592. {
  593. FlowId = flowId;
  594. FlowName = flowName;
  595. NodeId = System.Guid.NewGuid().ToString("N");
  596. // 绑定所属流程的结果注册表(setter 会同步到 PluginModel.Registry)
  597. Registry = registry ?? _registry ?? ResultRegistry.Debug;
  598. var model = PluginModel?.GetModel;
  599. if (model != null)
  600. {
  601. model.NodeId = NodeId;
  602. model.FlowId = FlowId;
  603. model.FlowName = FlowName;
  604. model.NodeName = NodeName;
  605. }
  606. // Group/ForLoop 子流程:递归初始化子节点并重映射子图连线
  607. if ((Category == NodeCategory.Group || Category == NodeCategory.ForLoop) && model != null)
  608. {
  609. // 反射查找 FlowGraph 类型的属性,遍历子节点初始化ID和Name
  610. var graphProp = model.GetType().GetProperties()
  611. .FirstOrDefault(p => p.PropertyType == typeof(FlowGraph));
  612. if (graphProp != null)
  613. {
  614. var subGraph = graphProp.GetValue(model) as FlowGraph;
  615. if (subGraph != null)
  616. {
  617. subGraph.GraphId = NodeId;
  618. subGraph.GraphName = NodeName;
  619. subGraph.Registry = Registry; // 子流程与父流程共用同一注册表
  620. if (subGraph.Nodes != null)
  621. {
  622. var childIdMap = new Dictionary<string, string>();
  623. foreach (var subNode in subGraph.Nodes)
  624. {
  625. string oldSubNodeId = subNode.NodeId;
  626. subNode.InitializeNode(NodeId, NodeName, Registry);
  627. if (!string.IsNullOrEmpty(oldSubNodeId))
  628. {
  629. childIdMap[oldSubNodeId] = subNode.NodeId;
  630. }
  631. }
  632. // 重映射子图连接线的 SourceNodeId/TargetNodeId
  633. if (subGraph.Connections != null && childIdMap.Count > 0)
  634. {
  635. foreach (var conn in subGraph.Connections)
  636. {
  637. if (childIdMap.TryGetValue(conn.SourceNodeId, out var newSrcId))
  638. conn.SourceNodeId = newSrcId;
  639. if (childIdMap.TryGetValue(conn.TargetNodeId, out var newTgtId))
  640. conn.TargetNodeId = newTgtId;
  641. }
  642. }
  643. }
  644. }
  645. }
  646. }
  647. ExecutionHistory?.Clear();
  648. // 恢复运行态集合([NonSerialized] 字段经 BinaryFormatter 反序列化后为 null)
  649. if (_resultItems == null)
  650. _resultItems = new ObservableCollection<ResultItem>();
  651. if (_executionHistory == null)
  652. _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
  653. // 关键修复:反序列化直接恢复私有字段 _PluginModel,不会经过 PluginModel 的 setter,
  654. // 因此这里统一补跑插件初始化(InitPlugin + 将默认输出注册到 ResultRegistry),
  655. // 保证导入/打开方案/粘贴后的节点与编辑器新建节点行为一致。
  656. if (PluginModel != null)
  657. {
  658. PluginModel.InitRun();
  659. // 与新建节点一致:默认输出同步到 UI 结果列表(仅在集合尚未建立时执行,避免重复历史记录)
  660. if (_resultItems.Count == 0 && _executionHistory.Count == 0)
  661. SetResults(FlowId, PluginModel.LastResults);
  662. }
  663. }
  664. }
  665. [Serializable]
  666. /// <summary>
  667. /// 执行历史记录项
  668. /// </summary>
  669. public class ExecutionHistoryEntry : TeamAAS.BindableBase
  670. {
  671. public DateTime Timestamp { get; }
  672. public string TimestampText => Timestamp.ToString("HH:mm:ss");
  673. public string FullTimestampText => Timestamp.ToString("HH:mm:ss.fff");
  674. private ObservableCollection<ResultItem> _results;
  675. public ObservableCollection<ResultItem> Results
  676. {
  677. get => _results;
  678. set => SetProperty(ref _results, value);
  679. }
  680. public ExecutionHistoryEntry(DateTime timestamp, ObservableCollection<ResultItem> results)
  681. {
  682. Timestamp = timestamp;
  683. _results = results;
  684. }
  685. }
  686. [Serializable]
  687. /// <summary>
  688. /// 执行结果项(UI展示用)
  689. /// </summary>
  690. public class ResultItem
  691. {
  692. public string Key { get; set; }
  693. public object Value { get; set; }
  694. /// <summary>
  695. /// 显式类型名:大对象被转成字符串展示时,记录转换前的实际类型;
  696. /// 未显式设置时按 Value 运行时类型推断。
  697. /// </summary>
  698. private string _typeName;
  699. public string TypeName
  700. {
  701. get => _typeName ?? (Value == null ? "null" : DescribeType(Value.GetType()));
  702. set => _typeName = value;
  703. }
  704. /// <summary>类型名格式化(泛型显示为 List&lt;T&gt; 形式)</summary>
  705. internal static string DescribeType(Type t)
  706. {
  707. if (t.IsGenericType)
  708. {
  709. var args = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
  710. return t.Name.Split('`')[0] + "<" + args + ">";
  711. }
  712. return t.Name;
  713. }
  714. /// <summary>简单类型的字符串展示</summary>
  715. public string DisplayValue
  716. {
  717. get
  718. {
  719. if (Value == null) return "";
  720. if (Value is System.Collections.IList list && !(Value is string))
  721. return list.Count + " 项";
  722. if (IsComplex) return "";
  723. return Value?.ToString() ?? "";
  724. }
  725. }
  726. /// <summary>是否为复杂对象(需展开子属性)</summary>
  727. public bool IsComplex => Value != null && !IsSimpleType(Value.GetType());
  728. private static bool IsSimpleType(Type type)
  729. {
  730. return type.IsPrimitive
  731. || type.IsEnum
  732. || type == typeof(string)
  733. || type == typeof(decimal)
  734. || type == typeof(DateTime);
  735. }
  736. private ObservableCollection<ResultItem> _subItems;
  737. public ObservableCollection<ResultItem> SubItems
  738. {
  739. get
  740. {
  741. if (_subItems == null && IsComplex)
  742. {
  743. _subItems = new ObservableCollection<ResultItem>();
  744. // 列表/数组:按索引展开元素
  745. if (Value is System.Collections.IList list && !(Value is string))
  746. {
  747. for (int i = 0; i < list.Count; i++)
  748. {
  749. _subItems.Add(new ResultItem { Key = "[" + i + "]", Value = list[i] });
  750. }
  751. }
  752. else
  753. {
  754. // 普通对象:反射公开属性
  755. foreach (var prop in Value.GetType().GetProperties(
  756. System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
  757. {
  758. try
  759. {
  760. var val = prop.GetValue(Value);
  761. _subItems.Add(new ResultItem { Key = prop.Name, Value = val });
  762. }
  763. catch { }
  764. }
  765. }
  766. }
  767. return _subItems ?? new ObservableCollection<ResultItem>();
  768. }
  769. }
  770. }
  771. }