| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- using Prism.Mvvm;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Diagnostics.Tracing;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.Threading;
- using System.Windows;
- using System.Windows.Media;
- using Newtonsoft.Json;
- using TeamAAS.FlowEditor.Execution;
- using TeamAAS.FlowEditor.Plugins;
- namespace TeamAAS.FlowEditor.Models
- {
- [Serializable]
- /// <summary>
- /// 流程节点基类 - 所有节点类型的公共属性
- /// </summary>
- public class FlowNode : TeamAAS.BindableBase
- {
- #region 标识属性
- [Category("II.杂项")]
- [DisplayName("1.节点ID")]
- [ReadOnly(true)]
- [Browsable(true)]
- public string NodeId { get; set; } = System.Guid.NewGuid().ToString("N");
- private string _nodeName = "新节点";
- [Category("I.节点参数")]
- [DisplayName("1.节点名称")]
- [Description("设置流程名称")]
- [Browsable(true)]
- /// <summary>
- /// 节点唯一ID(用于连接线关联,自动生成)
- /// </summary>
- public string NodeName
- {
- get => _nodeName;
- set
- {
- if (SetProperty(ref _nodeName, value))
- {
- if (PluginModel?.GetModel != null)
- PluginModel.GetModel.NodeName = value;
- }
- }
- }
- [Category("II.杂项")]
- [DisplayName("2.工具ID")]
- [ReadOnly(true)]
- [Browsable(true)]
- /// <summary>
- /// 关联的插件ID(兼容旧代码) 不变
- /// </summary>
- public string PluginId { get; set; }
- /// <summary>
- /// 所属流程绑定的结果注册表(运行态=Main,编辑/调试态=Debug)。
- /// 由 FlowGraph/InitializeNode 注入,并同步到 PluginModel.Registry。
- /// </summary>
- [JsonIgnore]
- [ReadOnly(true)]
- [Browsable(false)]
- public ResultRegistry Registry
- {
- get => _registry;
- set
- {
- _registry = value;
- if (PluginModel != null)
- PluginModel.Registry = value;
- }
- }
- [NonSerialized]
- private ResultRegistry _registry;
- [Category("II.杂项")]
- [DisplayName("3.工具名称")]
- [Description("设置工具名称")]
- [Browsable(true)]
- [ReadOnly(true)]
- /// <summary>
- /// 工具名称
- /// </summary>
- public string ToolName
- {
- get => PluginModel?.GetModel?.ToolName;
- set
- {
- if (PluginModel?.GetModel != null)
- PluginModel.GetModel.ToolName = value;
- }
- }
- private string _flowId;
- [Category("II.杂项")]
- [DisplayName("4.流程ID")]
- [ReadOnly(true)]
- [Browsable(true)]
- /// <summary>
- /// 所属流程ID(用于结果注册表和公式解析)
- /// </summary>
- public string FlowId
- {
- get => _flowId;
- set
- {
- if (_flowId != value)
- {
- _flowId = value;
- if (PluginModel?.GetModel != null)
- PluginModel.GetModel.FlowId = value;
- }
- }
- }
- private string _flowName;
- [Category("II.杂项")]
- [DisplayName("5.流程名称")]
- [ReadOnly(true)]
- [Browsable(true)]
- /// <summary>
- /// 所属流程名称(用于UI展示和公式解析)
- /// </summary>
- public string FlowName
- {
- get => _flowName;
- set
- {
- if (_flowName != value)
- {
- _flowName = value;
- if (PluginModel?.GetModel != null)
- PluginModel.GetModel.FlowName = value;
- }
- }
- }
- /// <summary>节点是否启用(委托到 PluginModel.IsEnable,禁用后不执行+画布变灰)</summary>
- [Category("I.节点参数")]
- [DisplayName("2.启用工具")]
- [Description("是否启用工具")]
- [Browsable(true)]
- public bool IsEnabled
- {
- get => PluginModel?.IsEnable ?? true;
- set
- {
- if (PluginModel != null && PluginModel.IsEnable != value)
- {
- PluginModel.IsEnable = value;
- RaisePropertyChanged(nameof(IsEnabled));
- }
- }
- }
- [Browsable(false)]
- /// <summary>
- /// 节点分类(用于UI展示不同形状和颜色)
- /// </summary>
- public NodeCategory Category { get; set; } = NodeCategory.Normal;
- private IFlowNodePlugin _PluginModel { get; set; }
- [Browsable(false)]
- /// <summary>
- /// 插件模型数据(序列化存储,类型为 BasePluginModel 派生类)
- /// </summary>
- public IFlowNodePlugin PluginModel
- {
- get => _PluginModel;
- set
- {
- if (value != null && _PluginModel != value)
- {
- _PluginModel = value;
- //_PluginModel.InitRun();
- // 同步 FlowNode 当前属性到模型
- _PluginModel.Registry = _registry;
- _PluginModel.GetModel.NodeName = _nodeName;
- if (_flowName != null) _PluginModel.GetModel.FlowName = _flowName;
- if (_flowId != null) _PluginModel.GetModel.FlowId = _flowId;
- SetResults(FlowId, _PluginModel.LastResults);
- }
- }
- }
- [Category("I.节点参数")]
- [DisplayName("3.跳过警告")]
- [Description("是否跳过返回值为 Warning 的返回任务")]
- [Browsable(true)]
- public bool IsSkipWarning
- {
- get; set;
- } = false;
- private int _preDelayMs = 0;
- [Category("I.节点参数")]
- [DisplayName("4.运行前休眠(ms)")]
- [Description("节点执行前等待的毫秒数。默认0=不等待,最小0ms。")]
- [Browsable(true)]
- public int PreDelayMs
- {
- get => _preDelayMs;
- set => _preDelayMs = value <= 0 ? 0 : value;
- }
- private int _postDelayMs = 0;
- [Category("I.节点参数")]
- [DisplayName("5.运行后休眠(ms)")]
- [Description("节点执行完成后等待的毫秒数,再启动后继节点。默认0=不等待,最小0ms。")]
- [Browsable(true)]
- public int PostDelayMs
- {
- get => _postDelayMs;
- set => _postDelayMs = value <= 0 ? 0 : value;
- }
- private int _timeoutMs = 0;
- [Category("I.节点参数")]
- [DisplayName("运行超时(ms)")]
- [Description("节点执行超时看门狗:超过该毫秒数仍未返回则判定为超时失败并继续后续流程(防止硬件卡死拖垮整条流程)。默认0=不限时。注:阻塞式硬件调用无法被强制中断,超时仅保证流程不再无限等待。")]
- [Browsable(true)]
- public int TimeoutMs
- {
- get => _timeoutMs;
- set => _timeoutMs = value <= 0 ? 0 : value;
- }
- /// <summary>
- /// 任务插件「日志存储详细度」(委托到 PluginModel.GetModel.StoreVerbosity,实际存储与序列化在模型侧)。
- /// 粗略=只写 L1/L2 到公共日志;详细=额外把 L3/L4 写到独立路径。
- /// </summary>
- [Category("I.节点参数")]
- [DisplayName("6.日志存储")]
- [Description("日志存储详细度:粗略=只写公共日志(L1/L2);详细=额外把 L3/L4 写到 流程\\任务 独立日志")]
- [Browsable(true)]
- public TeamAAS.Logging.LogVerbosity StoreVerbosity
- {
- get => PluginModel?.GetModel?.StoreVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略;
- set
- {
- if (PluginModel?.GetModel != null && PluginModel.GetModel.StoreVerbosity != value)
- {
- PluginModel.GetModel.StoreVerbosity = value;
- RaisePropertyChanged(nameof(StoreVerbosity));
- }
- }
- }
- /// <summary>
- /// 任务插件「日志显示详细度」(委托到 PluginModel.GetModel.DisplayVerbosity,实际存储在模型侧)。
- /// 粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(与是否落盘无关)。
- /// </summary>
- [Category("I.节点参数")]
- [DisplayName("7.日志显示")]
- [Description("日志显示详细度:粗略=实时日志只显 L1/L2;详细=额外显 L3/L4(独立于存储)")]
- [Browsable(true)]
- public TeamAAS.Logging.LogVerbosity DisplayVerbosity
- {
- get => PluginModel?.GetModel?.DisplayVerbosity ?? TeamAAS.Logging.LogVerbosity.粗略;
- set
- {
- if (PluginModel?.GetModel != null && PluginModel.GetModel.DisplayVerbosity != value)
- {
- PluginModel.GetModel.DisplayVerbosity = value;
- RaisePropertyChanged(nameof(DisplayVerbosity));
- }
- }
- }
- #endregion
- #region 布局属性
- [Browsable(false)]
- public double X
- {
- get => _x;
- set => SetProperty(ref _x, value);
- }
- private double _x = 100;
- [Browsable(false)]
- public double Y
- {
- get => _y;
- set => SetProperty(ref _y, value);
- }
- private double _y = 100;
- #endregion
- #region 运行属性
- [Browsable(false)]
- [JsonIgnore]
- public NodeRunStatus Status
- {
- get => _status;
- set => SetProperty(ref _status, value);
- }
- [NonSerialized]
- private NodeRunStatus _status = NodeRunStatus.NotStarted;
- [Browsable(false)]
- [JsonIgnore]
- public int CostTime
- {
- get => _costTime;
- set => SetProperty(ref _costTime, value);
- }
- [NonSerialized]
- private int _costTime;
- #endregion
- #region 异常分支触发状态(运行时,不序列化)
- [NonSerialized] private int _exceptionTriggers;
- [NonSerialized] private int _exceptionRunFlag;
- [Browsable(false)]
- [JsonIgnore]
- public int ExceptionTriggers => _exceptionTriggers;
- /// <summary>原子 +1 触发计数,返回新值。用于异常分支节点排队触发。</summary>
- public int IncrementExceptionTrigger() => Interlocked.Increment(ref _exceptionTriggers);
- /// <summary>原子 -1 触发计数,返回新值。每次实际执行完一次后调用。</summary>
- public int DecrementExceptionTrigger() => Interlocked.Decrement(ref _exceptionTriggers);
- /// <summary>尝试获取运行权:CAS 把 _exceptionRunFlag 从 0 改为 1。成功=true 表示获得运行权。</summary>
- public bool TryAcquireExceptionRun() =>
- Interlocked.CompareExchange(ref _exceptionRunFlag, 1, 0) == 0;
- /// <summary>释放运行权:把 _exceptionRunFlag 重置为 0。</summary>
- public void ReleaseExceptionRun() => Interlocked.Exchange(ref _exceptionRunFlag, 0);
- /// <summary>重置异常触发状态(每次流程开始 ResetAllNodes 时调用)。</summary>
- public void ResetExceptionState()
- {
- Interlocked.Exchange(ref _exceptionTriggers, 0);
- Interlocked.Exchange(ref _exceptionRunFlag, 0);
- }
- #endregion
- #region 执行结果
- [NonSerialized]
- private ObservableCollection<ResultItem> _resultItems = new ObservableCollection<ResultItem>();
- [Browsable(false)]
- [JsonIgnore]
- public ObservableCollection<ResultItem> ResultItems
- {
- get => _resultItems;
- set => SetProperty(ref _resultItems, value);
- }
- /// <summary>
- /// 执行历史记录(每次执行追加一条)
- /// </summary>
- [NonSerialized]
- private ObservableCollection<ExecutionHistoryEntry> _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
- [Browsable(false)]
- [JsonIgnore]
- public ObservableCollection<ExecutionHistoryEntry> ExecutionHistory
- {
- get => _executionHistory;
- set => SetProperty(ref _executionHistory, value);
- }
- /// <summary>
- /// 当前选中的历史记录(UI展示用)
- /// </summary>
- [NonSerialized]
- private ExecutionHistoryEntry _selectedHistoryEntry;
- [Browsable(false)]
- [JsonIgnore]
- public ExecutionHistoryEntry SelectedHistoryEntry
- {
- get => _selectedHistoryEntry;
- set => SetProperty(ref _selectedHistoryEntry, value);
- }
- [Browsable(false)]
- /// <summary>
- /// 历史记录最大保留条数(默认50,可外部设置)
- /// </summary>
- public int MaxHistoryCount { get; set; } = 50;
- /// <summary>
- /// 节点最新执行结果原始字典(供公式引用,不参与序列化)
- /// </summary>
- [Browsable(false)]
- [JsonIgnore]
- public Dictionary<string, object> RawResults { get; set; }
- private static bool IsBasicType(object value)
- {
- if (value == null) return true;
- var t = value.GetType();
- return t.IsPrimitive || t == typeof(string) || t == typeof(decimal) ||
- t == typeof(DateTime) || t == typeof(TimeSpan) || t == typeof(Guid) || t.IsEnum;
- }
- /// <summary>
- /// 设置执行结果(UI线程调用)
- /// </summary>
- public void SetResults(string GraphId, Dictionary<string, object> results)
- {
- if (_resultItems == null)
- _resultItems = new ObservableCollection<ResultItem>();
- _resultItems?.Clear();
- if (results != null)
- {
- foreach (var kv in results)
- {
- // 非基础类型只存 ToString,避免图像/CogRecord 等大对象挂在 UI 和历史快照里
- // RawResults 保留原始引用供节点间公式解析使用
- bool converted = kv.Value != null && !IsBasicType(kv.Value);
- _resultItems?.Add(new ResultItem
- {
- Key = kv.Key,
- Value = converted ? kv.Value.ToString() : kv.Value,
- // 转换后 TypeName 显式记录原始实际类型,避免显示成 String
- TypeName = converted ? ResultItem.DescribeType(kv.Value.GetType()) : null
- });
- }
- }
- RaisePropertyChanged(nameof(ResultItems));
- RaisePropertyChanged(nameof(ExecutionHistory));
- RaisePropertyChanged(nameof(SelectedHistoryEntry));
- // 添加到历史记录
- var snapshot = new ObservableCollection<ResultItem>();
- foreach (var item in _resultItems)
- snapshot.Add(new ResultItem { Key = item.Key, Value = item.Value, TypeName = item.TypeName });
- var entry = new ExecutionHistoryEntry(DateTime.Now, snapshot);
- if (_executionHistory == null)
- _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
- Application.Current?.Dispatcher.Invoke(() => _executionHistory.Insert(0, entry));
- // 超过最大条数自动清理最旧的(末尾)
- while (_executionHistory.Count > MaxHistoryCount)
- Application.Current?.Dispatcher.Invoke(() => _executionHistory.RemoveAt(_executionHistory.Count - 1));
- SelectedHistoryEntry = entry; // 自动选最新
- RawResults = results;
- }
- #endregion
- #region UI辅助属性
- [Browsable(false)]
- /// <summary>
- /// 节点图标(Material Design 图标 Kind 名,如 "Camera";由 MahApps IconPacks 渲染)
- /// </summary>
- public string IconGeometry { get; set; } = "CircleOutline";
- [Browsable(false)]
- [JsonIgnore]
- public bool IsSelected
- {
- get => _isSelected;
- set
- {
- if (SetProperty(ref _isSelected, value))
- RaisePropertyChanged(nameof(BorderColor));
- }
- }
- [NonSerialized]
- private bool _isSelected;
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 边框颜色(选中时高亮)
- /// </summary>
- public string BorderColor => IsSelected ? "#007ACC" : "#3F3F46";
- /// <summary>
- /// 节点宽度(可被实际渲染尺寸覆盖)
- /// </summary>
- [NonSerialized]
- private double _nodeWidth = 0;
- [Browsable(false)]
- public double NodeWidth
- {
- get
- {
- if (_nodeWidth > 0) return _nodeWidth;
- switch (Category)
- {
- case NodeCategory.Decision: return 180;
- case NodeCategory.ForLoop: return 180;
- case NodeCategory.Group: return 200;
- case NodeCategory.End: return 140;
- case NodeCategory.ExceptionBranch: return 160;
- default: return 160;
- }
- }
- set => _nodeWidth = value;
- }
- /// <summary>
- /// 节点高度(可被实际渲染尺寸覆盖)
- /// </summary>
- [NonSerialized]
- private double _nodeHeight = 0;
- [Browsable(false)]
- public double NodeHeight
- {
- get
- {
- if (_nodeHeight > 0) return _nodeHeight;
- switch (Category)
- {
- case NodeCategory.Decision: return 66;
- case NodeCategory.ForLoop: return 66;
- case NodeCategory.Group: return 66;
- case NodeCategory.End: return 52;
- case NodeCategory.ExceptionBranch: return 66;
- default: return 66;
- }
- }
- set => _nodeHeight = value;
- }
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 是否有第二个输出端口(判断节点)
- /// </summary>
- public System.Windows.Visibility HasSecondOutput
- {
- get
- {
- return Category == NodeCategory.Decision
- ? System.Windows.Visibility.Visible
- : System.Windows.Visibility.Collapsed;
- }
- }
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 分类对应的标题色(十六进制)
- /// </summary>
- public string CategoryColor
- {
- get
- {
- switch (Category)
- {
- case NodeCategory.Decision: return "#C2771A";
- case NodeCategory.ToolBlock: return "#7B1FA2";
- case NodeCategory.ForLoop: return "#00897B";
- case NodeCategory.Group: return "#E65100";
- case NodeCategory.End: return "#D32F2F";
- case NodeCategory.ExceptionBranch: return "#B71C1C";
- default: return "#007ACC";
- }
- }
- }
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 状态文本(属性面板用)
- /// </summary>
- public string StatusText
- {
- get
- {
- switch (Status)
- {
- case NodeRunStatus.NotStarted: return "未运行";
- case NodeRunStatus.Running: return "运行中...";
- case NodeRunStatus.Success: return "成功";
- case NodeRunStatus.Failed: return "失败";
- case NodeRunStatus.Skipped: return "跳过";
- default: return "";
- }
- }
- }
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 耗时显示文本(节点上显示)
- /// </summary>
- public string CostTimeText
- {
- get
- {
- if (CostTime == 0) return "0ms";
- if (CostTime < 1000) return CostTime + "ms";
- return (CostTime / 1000.0).ToString("F1") + "s";
- }
- }
- [Browsable(false)]
- [JsonIgnore]
- /// <summary>
- /// 状态指示色(十六进制)
- /// </summary>
- public string StatusColor
- {
- get
- {
- switch (Status)
- {
- case NodeRunStatus.NotStarted: return "#888888";
- case NodeRunStatus.Running: return "#FF9800";
- case NodeRunStatus.Success: return "#4CAF50";
- case NodeRunStatus.Failed: return "#F44336";
- case NodeRunStatus.Skipped: return "#666666";
- default: return "#888888";
- }
- }
- }
- public void NotifyStatusChanged()
- {
- RaisePropertyChanged(nameof(StatusText));
- RaisePropertyChanged(nameof(StatusColor));
- RaisePropertyChanged(nameof(CostTimeText));
- //RaisePropertyChanged(nameof(PropertySummary));
- RaisePropertyChanged(nameof(ResultItems));
- }
- #endregion
- public FlowNode()
- {
- }
- /// <summary>
- /// 初始化ID链接。
- /// 导入 .aas / 打开产品流程 / 复制粘贴等所有加载路径最终都会经过这里,
- /// 是节点"重挂接"的唯一入口;插件初始化(InitPlugin + 注册默认输出)也在这里统一补跑。
- /// </summary>
- /// <param name="flowId"></param>
- /// <param name="flowName"></param>
- public void InitializeNode(string flowId, string flowName, ResultRegistry registry = null)
- {
- FlowId = flowId;
- FlowName = flowName;
- NodeId = System.Guid.NewGuid().ToString("N");
- // 绑定所属流程的结果注册表(setter 会同步到 PluginModel.Registry)
- Registry = registry ?? _registry ?? ResultRegistry.Debug;
- var model = PluginModel?.GetModel;
- if (model != null)
- {
- model.NodeId = NodeId;
- model.FlowId = FlowId;
- model.FlowName = FlowName;
- model.NodeName = NodeName;
- }
- // Group/ForLoop 子流程:递归初始化子节点并重映射子图连线
- if ((Category == NodeCategory.Group || Category == NodeCategory.ForLoop) && model != null)
- {
- // 反射查找 FlowGraph 类型的属性,遍历子节点初始化ID和Name
- var graphProp = model.GetType().GetProperties()
- .FirstOrDefault(p => p.PropertyType == typeof(FlowGraph));
- if (graphProp != null)
- {
- var subGraph = graphProp.GetValue(model) as FlowGraph;
- if (subGraph != null)
- {
- subGraph.GraphId = NodeId;
- subGraph.GraphName = NodeName;
- subGraph.Registry = Registry; // 子流程与父流程共用同一注册表
- if (subGraph.Nodes != null)
- {
- var childIdMap = new Dictionary<string, string>();
- foreach (var subNode in subGraph.Nodes)
- {
- string oldSubNodeId = subNode.NodeId;
- subNode.InitializeNode(NodeId, NodeName, Registry);
- if (!string.IsNullOrEmpty(oldSubNodeId))
- {
- childIdMap[oldSubNodeId] = subNode.NodeId;
- }
- }
- // 重映射子图连接线的 SourceNodeId/TargetNodeId
- if (subGraph.Connections != null && childIdMap.Count > 0)
- {
- foreach (var conn in subGraph.Connections)
- {
- if (childIdMap.TryGetValue(conn.SourceNodeId, out var newSrcId))
- conn.SourceNodeId = newSrcId;
- if (childIdMap.TryGetValue(conn.TargetNodeId, out var newTgtId))
- conn.TargetNodeId = newTgtId;
- }
- }
- }
- }
- }
- }
- ExecutionHistory?.Clear();
- // 恢复运行态集合([NonSerialized] 字段经 BinaryFormatter 反序列化后为 null)
- if (_resultItems == null)
- _resultItems = new ObservableCollection<ResultItem>();
- if (_executionHistory == null)
- _executionHistory = new ObservableCollection<ExecutionHistoryEntry>();
- // 关键修复:反序列化直接恢复私有字段 _PluginModel,不会经过 PluginModel 的 setter,
- // 因此这里统一补跑插件初始化(InitPlugin + 将默认输出注册到 ResultRegistry),
- // 保证导入/打开方案/粘贴后的节点与编辑器新建节点行为一致。
- if (PluginModel != null)
- {
- PluginModel.InitRun();
- // 与新建节点一致:默认输出同步到 UI 结果列表(仅在集合尚未建立时执行,避免重复历史记录)
- if (_resultItems.Count == 0 && _executionHistory.Count == 0)
- SetResults(FlowId, PluginModel.LastResults);
- }
- }
- }
- [Serializable]
- /// <summary>
- /// 执行历史记录项
- /// </summary>
- public class ExecutionHistoryEntry : TeamAAS.BindableBase
- {
- public DateTime Timestamp { get; }
- public string TimestampText => Timestamp.ToString("HH:mm:ss");
- public string FullTimestampText => Timestamp.ToString("HH:mm:ss.fff");
- private ObservableCollection<ResultItem> _results;
- public ObservableCollection<ResultItem> Results
- {
- get => _results;
- set => SetProperty(ref _results, value);
- }
- public ExecutionHistoryEntry(DateTime timestamp, ObservableCollection<ResultItem> results)
- {
- Timestamp = timestamp;
- _results = results;
- }
- }
- [Serializable]
- /// <summary>
- /// 执行结果项(UI展示用)
- /// </summary>
- public class ResultItem
- {
- public string Key { get; set; }
- public object Value { get; set; }
- /// <summary>
- /// 显式类型名:大对象被转成字符串展示时,记录转换前的实际类型;
- /// 未显式设置时按 Value 运行时类型推断。
- /// </summary>
- private string _typeName;
- public string TypeName
- {
- get => _typeName ?? (Value == null ? "null" : DescribeType(Value.GetType()));
- set => _typeName = value;
- }
- /// <summary>类型名格式化(泛型显示为 List<T> 形式)</summary>
- internal static string DescribeType(Type t)
- {
- if (t.IsGenericType)
- {
- var args = string.Join(", ", t.GetGenericArguments().Select(a => a.Name));
- return t.Name.Split('`')[0] + "<" + args + ">";
- }
- return t.Name;
- }
- /// <summary>简单类型的字符串展示</summary>
- public string DisplayValue
- {
- get
- {
- if (Value == null) return "";
- if (Value is System.Collections.IList list && !(Value is string))
- return list.Count + " 项";
- if (IsComplex) return "";
- return Value?.ToString() ?? "";
- }
- }
- /// <summary>是否为复杂对象(需展开子属性)</summary>
- public bool IsComplex => Value != null && !IsSimpleType(Value.GetType());
- private static bool IsSimpleType(Type type)
- {
- return type.IsPrimitive
- || type.IsEnum
- || type == typeof(string)
- || type == typeof(decimal)
- || type == typeof(DateTime);
- }
- private ObservableCollection<ResultItem> _subItems;
- public ObservableCollection<ResultItem> SubItems
- {
- get
- {
- if (_subItems == null && IsComplex)
- {
- _subItems = new ObservableCollection<ResultItem>();
- // 列表/数组:按索引展开元素
- if (Value is System.Collections.IList list && !(Value is string))
- {
- for (int i = 0; i < list.Count; i++)
- {
- _subItems.Add(new ResultItem { Key = "[" + i + "]", Value = list[i] });
- }
- }
- else
- {
- // 普通对象:反射公开属性
- foreach (var prop in Value.GetType().GetProperties(
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
- {
- try
- {
- var val = prop.GetValue(Value);
- _subItems.Add(new ResultItem { Key = prop.Name, Value = val });
- }
- catch { }
- }
- }
- }
- return _subItems ?? new ObservableCollection<ResultItem>();
- }
- }
- }
- }
|