| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- using Prism.Mvvm;
- using Prism.Commands;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Windows;
- using TeamAAS.FlowEditor.Models;
- using TeamAAS.FlowEngine.Execution;
- using TeamAAS.Dialogs;
- using TeamAAS.FlowEngine;
- using TeamAAS.FlowEditor.Execution;
- namespace TeamAAS.FlowEditor
- {
- /// <summary>
- /// 流程编辑器外壳 ViewModel:管理多 Tab 流程
- /// </summary>
- public class FlowEditorShellViewModel : TeamAAS.BindableBase
- {
- public ObservableCollection<FlowTabItem> FlowTabs => PluginLoader.Instance.FlowTabs;
- private FlowTabItem _activeTab;
- public FlowTabItem ActiveTab
- {
- get => _activeTab;
- set
- {
- if (_activeTab != null) _activeTab.IsActive = false;
- if (SetProperty(ref _activeTab, value))
- {
- if (_activeTab != null) _activeTab.IsActive = true;
- RaisePropertyChanged(nameof(ActiveEditorVm));
- }
- }
- }
- public FlowEditorViewModel ActiveEditorVm => ActiveTab?.EditorVm;
- // 流程结构变化(新建/删除/重命名 Tab)——关闭的 Tab 不再参与 per-tab 脏检查,需单独记录
- private bool _structureChanged;
- private bool _isReadOnly;
- /// <summary>只读监控模式(运行产品运行中):由 ProductManager/ProductViewModel 设置并传播到所有 Tab</summary>
- public bool IsReadOnly
- {
- get => _isReadOnly;
- set
- {
- if (SetProperty(ref _isReadOnly, value))
- ApplyReadOnlyToTabs();
- }
- }
- private bool _isMonitorMode;
- /// <summary>是否监控运行中流程(勾选后挂接 RunTabs,否则独立编辑)</summary>
- public bool IsMonitorMode
- {
- get => _isMonitorMode;
- private set => SetProperty(ref _isMonitorMode, value);
- }
- /// <summary>切换监控模式(开/关)</summary>
- public DelegateCommand ToggleMonitorCommand { get; }
- private bool _isMonitorOptionAvailable;
- /// <summary>「监控当前流程」选项是否可用(当前产品==运行产品且正在运行)</summary>
- public bool IsMonitorOptionAvailable
- {
- get => _isMonitorOptionAvailable;
- private set => SetProperty(ref _isMonitorOptionAvailable, value);
- }
- /// <summary>把当前只读状态应用到所有流程 Tab(切换产品/新增 Tab 后调用)</summary>
- public void ApplyReadOnlyToTabs()
- {
- foreach (var tab in FlowTabs)
- tab.EditorVm?.SetReadOnly(_isReadOnly);
- }
- /// <summary>是否存在未保存的修改(流程内容修改 + 流程增删/改名)</summary>
- public bool HasUnsavedChanges =>
- _structureChanged || FlowTabs.Any(t => t.EditorVm != null && t.EditorVm.IsDirty);
- /// <summary>保存后清除所有流程的修改标记</summary>
- public void MarkAllClean()
- {
- _structureChanged = false;
- foreach (var tab in FlowTabs)
- tab.EditorVm?.MarkClean();
- }
- public DelegateCommand AddFlowCommand { get; }
- public DelegateCommand<FlowTabItem> CloseFlowCommand { get; }
- public DelegateCommand<FlowTabItem> RenameFlowCommand { get; }
- public DelegateCommand<FlowTabItem> OpenTabPropertiesCommand { get; }
- public DelegateCommand ImportFlowCommand { get; }
- public DelegateCommand ExportFlowCommand { get; }
- public FlowEditorShellViewModel()
- {
- if (FlowTabs.Count == 0)
- {
- var defaultTab = new FlowTabItem("流程1");
- FlowTabs.Add(defaultTab);
- ActiveTab = defaultTab;
- }
- else
- {
- ActiveTab = FlowTabs[0];
- }
- AddFlowCommand = new DelegateCommand(AddFlow);
- CloseFlowCommand = new DelegateCommand<FlowTabItem>(CloseFlow);
- RenameFlowCommand = new DelegateCommand<FlowTabItem>(tab => RenameFlow(tab));
- OpenTabPropertiesCommand = new DelegateCommand<FlowTabItem>(OpenTabProperties);
- // 导入/导出逻辑统一在 FlowEditorViewModel 的命令里,Shell 只做转发(只读模式下禁用导入)
- ImportFlowCommand = new DelegateCommand(
- () => ActiveEditorVm?.ImportFlowCommand?.Execute(),
- () => ActiveEditorVm != null && !IsReadOnly);
- ExportFlowCommand = new DelegateCommand(() => ActiveEditorVm?.ExportFlowCommand?.Execute());
- ToggleMonitorCommand = new DelegateCommand(ToggleMonitor);
- // 订阅运行态重载事件:监控中主界面重载流程时自动同步显示
- FlowRunner.Instance.RunTabsRefreshed += OnRunTabsRefreshed;
- }
- /// <summary>切换监控模式入口</summary>
- private void ToggleMonitor()
- {
- if (IsMonitorMode) ExitMonitor();
- else EnterMonitor();
- }
- /// <summary>进入监控模式:必要时提示保存当前编辑,再挂接运行态 RunTabs</summary>
- private void EnterMonitor()
- {
- // 运行态未启动 → 提示并 return
- if (!FlowRunner.Instance.IsRunning || FlowRunner.Instance.RunTabs.Count == 0)
- {
- DialogHelper.Info("当前没有正在运行的流程,无法监控");
- return;
- }
- // 当前产品 != 运行产品 → 提示保存后切换(点否不保存但仍然切到运行产品监控)
- if (!string.Equals(ProductManager.Instance.CurrentProductName,
- FlowRunner.Instance.RunProductName, StringComparison.Ordinal))
- {
- if (HasUnsavedChanges)
- {
- bool save = DialogHelper.ConfirmYesNo("切换到运行产品需先保存当前方案,是否保存?", "保存确认");
- if (save) SaveAllModifiedTabs();
- }
- ProductManager.Instance.SwitchProduct(FlowRunner.Instance.RunProductName, this);
- }
- else if (HasUnsavedChanges)
- {
- // 当前产品 == 运行产品:点否也进入监控,但不保存
- bool save = DialogHelper.ConfirmYesNo("进入监控前是否保存当前修改?", "保存确认");
- if (save) SaveAllModifiedTabs();
- }
- AttachRunTabs();
- IsMonitorMode = true;
- IsReadOnly = true;
- }
- /// <summary>退出监控模式:恢复编辑态(从磁盘重载副本)</summary>
- private void ExitMonitor()
- {
- IsMonitorMode = false;
- IsReadOnly = false;
- DetachAndReloadFromDisk();
- }
- /// <summary>
- /// 根据"当前产品 == 运行产品 且 正在运行"刷新监控选项可用性,并自动同步监控状态:
- /// - 满足条件 → 选项可用;若当前不在监控则自动进入监控
- /// - 不满足 → 选项禁用;若当前在监控则自动退出
- /// 该方法由 ProductManager.SwitchProduct/SwitchProductAsync、ProductViewModel.RefreshRunState 调用。
- /// 注意:本方法不直接调 EnterMonitor(避免与 EnterMonitor 内部的 SwitchProduct 互相递归),
- /// 而是设置 IsMonitorMode 标志后再调,让 EnterMonitor 看到 IsMonitorMode=true 时跳过自动调用。
- /// </summary>
- public void ApplyMonitorAvailability()
- {
- bool available = !string.IsNullOrEmpty(ProductManager.Instance.CurrentProductName)
- && string.Equals(ProductManager.Instance.CurrentProductName,
- FlowRunner.Instance.RunProductName, StringComparison.Ordinal)
- && FlowRunner.Instance.IsRunning;
- IsMonitorOptionAvailable = available;
- try
- {
- if (!available && IsMonitorMode)
- {
- // 当前产品不再是运行产品或运行已停止 → 退出监控
- ExitMonitor();
- }
- else if (available && !IsMonitorMode)
- {
- // 切到运行产品且正在运行 → 默认进入监控模式。
- // 先置 IsMonitorMode=true 防止 EnterMonitor 内部调 SwitchProduct 时
- // 其末尾的 ApplyMonitorAvailability 再次触发 EnterMonitor(无限递归)。
- IsMonitorMode = true;
- EnterMonitor();
- }
- }
- catch (Exception ex)
- {
- // 监控自动切换失败不应导致整个应用闪退
- IsMonitorMode = false;
- IsReadOnly = false;
- TeamAAS.AppLogger.Error("ApplyMonitorAvailability 自动监控失败", ex, nameof(FlowEditorShellViewModel));
- }
- }
- /// <summary>保存所有已修改的流程 tab 到磁盘</summary>
- private void SaveAllModifiedTabs()
- {
- foreach (var tab in FlowTabs)
- if (tab.EditorVm != null && tab.EditorVm.IsDirty)
- ProductManager.Instance.SaveFlow(ProductManager.Instance.CurrentProductName, tab);
- MarkAllClean();
- }
- /// <summary>
- /// 释放当前集合中的编辑态 Tab(插件 Dispose + Debug 注册表清理)。
- /// 跳过 FlowRunner.RunTabs 中的运行态 Tab——它们由 FlowRunner 拥有,可能正在执行,不能在此释放。
- /// </summary>
- private void DisposeEditTabs()
- {
- try
- {
- var runTabs = FlowRunner.Instance.RunTabs;
- var editTabs = FlowTabs.Where(t => t != null && !runTabs.Contains(t)).ToList();
- if (editTabs.Count > 0)
- ProductManager.DisposeFlowTabs(editTabs);
- }
- catch (Exception ex)
- {
- TeamAAS.AppLogger.Error("释放编辑态流程 Tab 失败", ex, nameof(FlowEditorShellViewModel));
- }
- }
- /// <summary>挂接运行态 RunTabs 到当前编辑器(监控模式核心)</summary>
- private void AttachRunTabs()
- {
- // 释放被替换的编辑态 Tab,否则插件与 Debug 注册表引用会随每次进入监控持续泄漏
- DisposeEditTabs();
- // 进入监控=换对象:完全清空 Debug 之前的编辑态集合(含子流程残留),
- // 此后编辑器挂的是 Main 的 RunTabs,看到的就是运行态数据
- ResultRegistry.Debug.ClearAllFlows();
- FlowTabs.Clear();
- foreach (var t in FlowRunner.Instance.RunTabs.ToList())
- FlowTabs.Add(t);
- if (FlowTabs.Count > 0) ActiveTab = FlowTabs[0];
- }
- /// <summary>从磁盘重载当前产品的编辑副本(退出监控时恢复编辑态)</summary>
- private void DetachAndReloadFromDisk()
- {
- var productName = ProductManager.Instance.CurrentProductName;
- // 退出监控时 FlowTabs 挂的是 RunTabs(由 FlowRunner 拥有),只解除引用不释放;
- // 非监控状态下的残留编辑 Tab 则正常释放
- DisposeEditTabs();
- // 退出监控=换回编辑态对象:完全清空 Debug(含监控期与子流程残留),再从磁盘重载干净副本
- ResultRegistry.Debug.ClearAllFlows();
- FlowTabs.Clear();
- if (!string.IsNullOrEmpty(productName))
- {
- foreach (var t in ProductManager.Instance.LoadAllFlows(productName))
- FlowTabs.Add(t);
- }
- if (FlowTabs.Count == 0)
- {
- var defaultTab = new FlowTabItem("流程1");
- FlowTabs.Add(defaultTab);
- }
- ActiveTab = FlowTabs.FirstOrDefault();
- }
- /// <summary>FlowRunner.RunTabsRefreshed 回调:监控模式下自动同步新运行态</summary>
- private void OnRunTabsRefreshed()
- {
- if (!IsMonitorMode) return;
- Application.Current?.Dispatcher.Invoke(AttachRunTabs);
- }
- public void AddFlow()
- {
- if (IsReadOnly) return; // 运行监控中禁止新增流程
- var name = GetUniqueFlowName("流程");
- var tab = new FlowTabItem(name);
- tab.EditorVm?.SetReadOnly(IsReadOnly);
- FlowTabs.Add(tab);
- ActiveTab = tab;
- _structureChanged = true; // 新增流程属于未保存修改
- }
- private string GetUniqueFlowName(string baseName)
- {
- int suffix = 1;
- var name = baseName + suffix;
- while (FlowTabs.Any(t => t.Name == name))
- {
- suffix++;
- name = baseName + suffix;
- }
- return name;
- }
- public void CloseFlow(FlowTabItem tab)
- {
- if (tab == null || FlowTabs.Count <= 1) return;
- if (IsReadOnly)
- {
- DialogHelper.Info("运行产品监控中,无法关闭流程");
- return;
- }
- // 关闭的流程如果有未保存修改,先确认是否放弃
- if (tab.EditorVm != null && tab.EditorVm.IsDirty)
- {
- bool discard = DialogHelper.ConfirmYesNo(
- $"流程 \"{tab.Name}\" 有未保存的修改,关闭后将丢失这些修改,确定关闭吗?",
- "关闭流程");
- if (!discard) return;
- }
- int idx = FlowTabs.IndexOf(tab);
- FlowTabs.Remove(tab);
- if (ActiveTab == tab)
- {
- int newIdx = Math.Min(idx, FlowTabs.Count - 1);
- ActiveTab = FlowTabs[newIdx];
- }
- _structureChanged = true; // 删除流程:保存时需要同步删除磁盘上的对应 .aas
- }
- public void RenameFlow(FlowTabItem tab, string newName = null)
- {
- if (tab == null) return;
- if (IsReadOnly)
- {
- DialogHelper.Info("运行产品监控中,无法重命名流程");
- return;
- }
- if (newName == null)
- {
- newName = ShowRenameDialog(tab.Name);
- if (string.IsNullOrWhiteSpace(newName)) return;
- }
- // 重名加序号(排除自身)
- if (FlowTabs.Any(t => t != tab && t.Name == newName))
- newName = GetUniqueFlowName(newName);
- tab.Name = newName;
- tab.Graph.GraphName = newName;
- // 同步所有节点的 FlowName
- foreach (var node in tab.Graph.Nodes)
- {
- node.FlowName = newName;
- }
- tab.EditorVm?.MarkDirty(); // 流程改名也属于未保存修改
- }
- public void OpenTabProperties(FlowTabItem tab)
- {
- if (tab == null) return;
- if (IsReadOnly)
- {
- DialogHelper.Info("运行产品监控中,无法修改流程属性");
- return;
- }
- // 调试:记录打开属性前的 TriggerType 值
- TeamAAS.AppLogger.Info($"OpenTabProperties: {tab.Name} 当前 TriggerType={tab.TriggerType}", "OpenTabProperties");
- if (DialogHelper.EditProperties(tab, $"流程属性 - {tab.Name}"))
- {
- // 调试:记录关闭对话框后的 TriggerType 值
- TeamAAS.AppLogger.Info($"OpenTabProperties: {tab.Name} 修改后 TriggerType={tab.TriggerType}", "OpenTabProperties");
- DialogHelper.Info($"已更新流程属性: {tab.Name}");
- }
- }
- private string ShowRenameDialog(string currentName)
- {
- return DialogHelper.Input("流程名称", "重命名流程", currentName);
- }
- }
- }
|