392 lines
13 KiB
C#
392 lines
13 KiB
C#
using MECF.Framework.Common.DataCenter;
|
||
using MECF.Framework.UI.Client.CenterViews.Configs.Roles;
|
||
using MECF.Framework.UI.Client.CenterViews.Editors.Sequence;
|
||
using MECF.Framework.UI.Client.ClientBase;
|
||
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
|
||
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
|
||
using MECF.Framework.UI.Core.Accounts;
|
||
using OpenSEMI.ClientBase;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Media;
|
||
using System.Windows.Threading;
|
||
|
||
namespace MECF.Framework.UI.Client.CenterViews.Editors.Recipe
|
||
{
|
||
public class RecipeCompareViewModel : UiViewModelBase
|
||
{
|
||
private readonly RecipeType _fileTypes;
|
||
private readonly bool _isFolderOnly;
|
||
private readonly bool _isShowRootNode;
|
||
private FileNode _currentFileNode;
|
||
private readonly RecipeProvider _recipeProvider;
|
||
|
||
public object View { get; set; }
|
||
|
||
public ObservableCollection<string> Chambers { get; set; }
|
||
|
||
private string labSelectRecipeNames;
|
||
|
||
public string LabSelectRecipeNames
|
||
{
|
||
get => labSelectRecipeNames;
|
||
set
|
||
{
|
||
labSelectRecipeNames = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
private RecipeData _recipeHeaders;
|
||
/// <summary>
|
||
/// Recipe表头
|
||
/// </summary>
|
||
public RecipeData RecipeHeaders
|
||
{
|
||
get => _recipeHeaders;
|
||
set
|
||
{
|
||
_recipeHeaders = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
private RecipeData _comparRecipeHeader;
|
||
/// <summary>
|
||
/// Recipe表头
|
||
/// </summary>
|
||
public RecipeData ComparRecipeHeader
|
||
{
|
||
get => _comparRecipeHeader;
|
||
set
|
||
{
|
||
_comparRecipeHeader = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
private RecipeData _comparRecipe1;
|
||
/// <summary>
|
||
/// 选择要Recipe-1
|
||
/// </summary>
|
||
public RecipeData ComparRecipe1
|
||
{
|
||
get => _comparRecipe1;
|
||
set
|
||
{
|
||
_comparRecipe1 = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
private RecipeData _comparRecipe2;
|
||
/// <summary>
|
||
/// 选择要Recipe-2
|
||
/// </summary>
|
||
public RecipeData ComparRecipe2
|
||
{
|
||
get => _comparRecipe2;
|
||
set
|
||
{
|
||
_comparRecipe2 = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
private double[] _scrollViewerOffset;
|
||
/// <summary>
|
||
/// ScrollViewer滑动偏移
|
||
/// </summary>
|
||
public double[] ScrollViewerOffset
|
||
{
|
||
get => _scrollViewerOffset;
|
||
set
|
||
{
|
||
_scrollViewerOffset = value;
|
||
NotifyOfPropertyChange();
|
||
}
|
||
}
|
||
|
||
public int ProcessTypeIndexSelection { get; set; }
|
||
public RecipeType CurrentProcessType => ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType;
|
||
|
||
public int ChamberTypeIndexSelection { get; set; }
|
||
public ObservableCollection<string> ChamberType { get; set; }
|
||
public string CurrentChamberType => ChamberType[ChamberTypeIndexSelection];
|
||
|
||
public string SelectedChamber { get; set; }
|
||
|
||
public ObservableCollection<FileNode> SelectRecipeNameList { get; set; } = new ObservableCollection<FileNode>();
|
||
|
||
public FileNode DGSelectRecipeName { get; set; }
|
||
|
||
public List<ProcessTypeFileItem> ProcessTypeFileList { get; set; }
|
||
|
||
public RecipeCompareViewModel()
|
||
{
|
||
_fileTypes = RecipeType.Process | RecipeType.Routine;
|
||
_isFolderOnly = false;
|
||
_isShowRootNode = false;
|
||
|
||
_recipeProvider = new RecipeProvider();
|
||
|
||
var chamberType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType");
|
||
if (chamberType == null)
|
||
{
|
||
ChamberType = new ObservableCollection<string>() { "Default" };
|
||
}
|
||
else
|
||
{
|
||
ChamberType = new ObservableCollection<string>(((string)(chamberType)).Split(','));
|
||
}
|
||
}
|
||
|
||
protected override void OnViewLoaded(object view)
|
||
{
|
||
View = view;
|
||
base.OnViewLoaded(view);
|
||
}
|
||
|
||
protected override void OnDeactivate(bool close)
|
||
{
|
||
base.OnDeactivate(close);
|
||
|
||
// 锁定编辑器
|
||
((RecipeCompareView)View).editorLocker.Lock();
|
||
}
|
||
|
||
protected override void OnInitialize()
|
||
{
|
||
base.OnInitialize();
|
||
BuildFileTree(_isShowRootNode, _isFolderOnly, _fileTypes);
|
||
}
|
||
|
||
private void BuildFileTree(bool isShowRootNode, bool isFolderOnly, RecipeType fileType)
|
||
{
|
||
var recipeProvider = new RecipeProvider();
|
||
var scProcessType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedProcessType").ToString();
|
||
var supportedRecipeType = new List<RecipeType>(new[] { RecipeType.Process, RecipeType.Routine });
|
||
|
||
if (!string.IsNullOrEmpty(scProcessType))
|
||
{
|
||
supportedRecipeType.Clear();
|
||
|
||
if (fileType.HasFlag(RecipeType.Process) && scProcessType.Contains(RecipeType.Process.ToString()))
|
||
supportedRecipeType.Add(RecipeType.Process);
|
||
|
||
if (fileType.HasFlag(RecipeType.Routine) && scProcessType.Contains(RecipeType.Routine.ToString()))
|
||
supportedRecipeType.Add(RecipeType.Routine);
|
||
}
|
||
|
||
var processTypeFileList = new List<ProcessTypeFileItem>();
|
||
|
||
for (var i = 0; i < supportedRecipeType.Count; i++)
|
||
{
|
||
var type = new ProcessTypeFileItem
|
||
{
|
||
ProcessType = supportedRecipeType[i]
|
||
};
|
||
var prefix = $"Sic\\{supportedRecipeType[i]}";
|
||
var recipes = recipeProvider.GetXmlRecipeList(prefix);
|
||
type.FileListByProcessType = RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes, isFolderOnly, isShowRootNode)[0].Files;
|
||
processTypeFileList.Add(type);
|
||
}
|
||
|
||
this.ProcessTypeFileList = processTypeFileList;
|
||
}
|
||
|
||
protected override void OnActivate()
|
||
{
|
||
UpdateRecipeFormat();
|
||
base.OnActivate();
|
||
}
|
||
|
||
public void UpdateRecipeFormat()
|
||
{
|
||
var chamber = QueryDataClient.Instance.Service.GetConfig("System.Recipe.ChamberModules");
|
||
if (chamber == null)
|
||
{
|
||
chamber = "PM1";
|
||
}
|
||
|
||
Chambers = new ObservableCollection<string>(((string)chamber).Split(','));
|
||
if (Chambers.Count > 1)
|
||
{
|
||
for (var i = 0; i < Chambers.Count; i++)
|
||
{
|
||
var isPmInstall =
|
||
(bool)QueryDataClient.Instance.Service.GetConfig(
|
||
$"System.SetUp.Is{Chambers[i].ToString()}Installed");
|
||
{
|
||
if (!isPmInstall)
|
||
{
|
||
Chambers.RemoveAt(i);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Chambers.Count == 0)
|
||
{
|
||
Chambers = new ObservableCollection<string>(new string[] { "PM1" });
|
||
}
|
||
|
||
SelectedChamber = Chambers[0];
|
||
|
||
#region Create RecipeData
|
||
|
||
ComparRecipeHeader = CreateRecipe();
|
||
ComparRecipeHeader.AddStep();
|
||
|
||
ComparRecipe1 = CreateRecipe();
|
||
ComparRecipe2 = CreateRecipe();
|
||
|
||
#endregion Create RecipeData
|
||
}
|
||
|
||
private RecipeData CreateRecipe()
|
||
{
|
||
|
||
var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId);
|
||
|
||
|
||
var recipeData = new RecipeData(null, CurrentProcessType);
|
||
recipeData.BuildFormat($"{CurrentChamberType}\\{CurrentProcessType}", SelectedChamber, role);
|
||
|
||
//得到当前登录的RoleItem
|
||
//var role = _roleManager.GetRoleByName(BaseApp.Instance.UserContext.RoleName);
|
||
|
||
return recipeData;
|
||
}
|
||
|
||
private async Task LoadRecipe(RecipeData recipeData, string prefixPath, string recipeName)
|
||
{
|
||
recipeData.Clear();
|
||
recipeData.Steps.Save(); // 重置为已保存状态
|
||
|
||
var recipeContent = _recipeProvider.ReadRecipeFile(prefixPath, recipeName);
|
||
|
||
if (string.IsNullOrEmpty(recipeContent))
|
||
{
|
||
MessageBox.Show($"{prefixPath}\\{recipeName} is empty, please confirm the file is valid.",
|
||
"Error",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
return;
|
||
}
|
||
var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId);
|
||
await recipeData.LoadFile(prefixPath, recipeName, recipeContent, SelectedChamber, role,
|
||
GetLoadingDispatcher());
|
||
}
|
||
|
||
private static Dispatcher GetLoadingDispatcher()
|
||
{
|
||
// 判断Step呈现方式
|
||
var isCascadeLoading = (bool)QueryDataClient.Instance.Service.GetConfig("System.RecipeCascadeLoading");
|
||
var dispatcher = isCascadeLoading ? Dispatcher.CurrentDispatcher : null;
|
||
return dispatcher;
|
||
}
|
||
|
||
public async void ComperSelectRecipe()
|
||
{
|
||
SelectRecipeNameList.Clear();
|
||
|
||
foreach (var ProcessTypeFile in ProcessTypeFileList)
|
||
{
|
||
GetSelectRecipeName(ProcessTypeFile.FileListByProcessType);
|
||
}
|
||
|
||
if (SelectRecipeNameList.Count != 2)
|
||
{
|
||
DialogBox.ShowError("Only 2 comparisons are supported");
|
||
return;
|
||
}
|
||
|
||
LabSelectRecipeNames = SelectRecipeNameList[0].Name + " / " + SelectRecipeNameList[1].Name;
|
||
|
||
|
||
await LoadRecipe(ComparRecipe1, GetRecipeFullPath(SelectRecipeNameList[0]), SelectRecipeNameList[0].Name);
|
||
|
||
await LoadRecipe(ComparRecipe2, GetRecipeFullPath(SelectRecipeNameList[1]), SelectRecipeNameList[1].Name);
|
||
|
||
StartComper(ComparRecipe1, ComparRecipe2);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归获取选择的Recipe
|
||
/// </summary>
|
||
/// <param name="fileNodes"></param>
|
||
private void GetSelectRecipeName(ObservableCollection<FileNode> fileNodes)
|
||
{
|
||
foreach (var item in fileNodes)
|
||
{
|
||
if (item.Files != null && item.Files.Count > 0)
|
||
GetSelectRecipeName(item.Files);
|
||
|
||
if (item.IsSelected)
|
||
SelectRecipeNameList.Add(item);
|
||
}
|
||
}
|
||
|
||
private string GetRecipeFullPath(FileNode fileNodes)
|
||
{
|
||
return fileNodes.PrefixPath + (fileNodes.Parent.FullPath == "" ? "" : "\\" + fileNodes.Parent.FullPath);
|
||
}
|
||
|
||
public void StartComper(RecipeData recipeData1, RecipeData recipeData2)
|
||
{
|
||
int stepsCount = 0;//缓存较短Recipe步数
|
||
RecipeData recipeDataExcess;//缓存较长步数Recipe
|
||
if (recipeData1.Steps.Count <= recipeData2.Steps.Count)
|
||
{
|
||
stepsCount = recipeData1.Steps.Count;
|
||
recipeDataExcess = recipeData2;
|
||
}
|
||
else
|
||
{
|
||
stepsCount = recipeData2.Steps.Count;
|
||
recipeDataExcess = recipeData1;
|
||
}
|
||
|
||
for (int i = 0; i < stepsCount; i++)
|
||
{
|
||
RecipeStep recipe1step = recipeData1.Steps[i];
|
||
RecipeStep recipe2step = recipeData2.Steps[i];
|
||
|
||
for (int j = 0; j < recipe1step.Count; j++)
|
||
{
|
||
object obj1 = GetValue(recipe1step[j]);//获取单元格中的数值,object类型
|
||
object obj2 = GetValue(recipe2step[j]);
|
||
|
||
if (!(Comparer<object>.Default.Compare(obj1, obj2) == 0))
|
||
{
|
||
//recipe1step[j].Highlight();//不同部分高亮
|
||
//recipe2step[j].Highlight();
|
||
|
||
recipe1step[j].CompareDifferent();
|
||
recipe2step[j].CompareDifferent();
|
||
}
|
||
}
|
||
}
|
||
|
||
//步数多余部分高亮显示 使用缓存步数和缓存Recipe
|
||
for (int i = stepsCount; i < recipeDataExcess.Steps.Count; i++)
|
||
{
|
||
for (int j = 0; j < recipeDataExcess.Steps[i].Count; j++)
|
||
{
|
||
recipeDataExcess.Steps[i][j].CompareDifferent();
|
||
}
|
||
}
|
||
}
|
||
|
||
private object GetValue(Param param)
|
||
{
|
||
object Value = null;
|
||
if (param is IValueParam vp)
|
||
Value = vp.GetValue();
|
||
|
||
return Value;
|
||
}
|
||
}
|
||
} |