Tz2/Assets/Zion/Scripts/ERP/库存物资库存地点批量切换/InventoryStockLocationBatch...

979 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using DefaultNamespace.ProcessMode;
using Framework.Manager;
using Framework.ProcessMode;
using MotionFramework;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Zion.Scripts.ERP.
{
/// <summary>
/// m过账转移new
/// </summary>
public class InventoryStockLocationBatchSwitcherManager : MonoBehaviour
{
public TMP_InputField[] materialCodes; // 物料编码输入框数组
public TMP_InputField[] storageLocations; // 库存地点输入框数组
public TMP_InputField[] factories; // 工厂输入框数组
public TMP_InputField[] movementReasons; // 移动原因输入框数组
public TMP_InputField[] quantities; // 数量输入框数组
public TMP_InputField[] batchs;//批次输入框数组
// 新增库存地点转移过帐输入框数组
public TMP_InputField[] stockLocationTransferPostings;
// 新增批次传输过帐输入框数组
public TMP_InputField[] batchTransferPostings;
public TMP_Dropdown quantitiesDropdown;
public TMP_Dropdown quantitiesDropdown2;
public Button jianchaButton;
public Button queryButton;
public TMP_Text titleText;
public AutoHideScript autoHideScript;
// 每行标准答案索引0对应"1行/1号"- 从框架动态获取
private string[] materialCodeAnswers = new string[0];
private string[] storageLocationAnswers = new string[0];
private string[] factoryAnswers = new string[0];
private string[] quantityAnswers = new string[0];
private string[] batchAnswers = new string[0];
// 无序绑定:行 -> 答案索引(确定该行属于"1号/2号/..."
private readonly Dictionary<int, int> rowToAnswerIndex = new Dictionary<int, int>();
// 字段验证状态跟踪:记录每个字段是否已经通过验证并触发过向导(用于避免重复触发)
// 键:(行索引, 字段标签),值:是否已经触发过向导
private readonly Dictionary<(int rowIndex, string fieldLabel), bool> fieldValidationStates = new Dictionary<(int, string), bool>();
// 已完成验证的字段集合:验证通过并触发向导后,不再跟踪这些字段的变化
private readonly HashSet<(int rowIndex, string fieldLabel)> completedFields = new HashSet<(int, string)>();
// 模式管理相关变量
private ProcessMode _cachedCurrentMode; // 缓存的当前模式
/// <summary>
/// 获取当前是否为考核模式
/// </summary>
private bool IsAssessmentMode => _cachedCurrentMode == ProcessMode.;
private void Start()
{
// 缓存当前模式
CacheCurrentMode();
// 初始化输入验证系统
InitializeInputValidation();
jianchaButton.onClick.AddListener(OnQueryButtonClicked);
queryButton.onClick.AddListener(delegate
{
if (MotionEngine.GetModule<ProcessManager>().HandleClick("过账"))
{
autoHideScript.ShowObject("已过账");
TutorialGuideManager.Instance.TriggerNextGuide(queryButton.name);
}
});
}
/// <summary>
/// 缓存当前模式
/// </summary>
private void CacheCurrentMode()
{
try
{
var processManager = MotionEngine.GetModule<ProcessManager>();
if (processManager != null)
{
_cachedCurrentMode = processManager._currentMode;
Debug.Log($"缓存当前模式: {_cachedCurrentMode}");
}
else
{
Debug.LogWarning("ProcessManager未找到使用默认模式");
_cachedCurrentMode = ProcessMode.; // 默认模式
}
}
catch (System.Exception ex)
{
Debug.LogError($"获取当前模式时发生错误: {ex.Message}");
_cachedCurrentMode = ProcessMode.; // 默认模式
}
}
/// <summary>
/// 初始化输入验证系统
/// </summary>
private void InitializeInputValidation()
{
// 从流程框架获取答案并解析
LoadAnswersFromFramework();
// 为所有输入框添加值变化监听
AddInputFieldListeners();
}
/// <summary>
/// 从流程框架获取当前动作的答案并解析
/// </summary>
private void LoadAnswersFromFramework()
{
try
{
var processManager = MotionEngine.GetModule<ProcessManager>();
if (processManager == null)
{
Debug.LogWarning("ProcessManager未找到使用默认空答案");
return;
}
// 通过反射获取当前动作
var currentStepIndexField = typeof(ProcessManager).GetField("_currentStepIndex",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var currentActionIndexField = typeof(ProcessManager).GetField("_currentActionIndex",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (currentStepIndexField != null && currentActionIndexField != null)
{
int currentStepIndex = (int)currentStepIndexField.GetValue(processManager);
int currentActionIndex = (int)currentActionIndexField.GetValue(processManager);
var currentProcessCollection = processManager.CurrentProcessCollection;
if (currentStepIndex >= 0 && currentStepIndex < currentProcessCollection.Steps.Count)
{
var currentStep = currentProcessCollection.Steps[currentStepIndex];
if (currentActionIndex >= 0 && currentActionIndex < currentStep.Actions.Count)
{
var currentAction = currentStep.Actions[currentActionIndex];
// 解析JudgmentQuestions
if (currentAction.JudgmentQuestions != null && currentAction.JudgmentQuestions.Count > 0)
{
ParseAnswersFromJudgmentQuestions(currentAction.JudgmentQuestions);
Debug.Log("从流程框架成功加载答案");
return;
}
}
}
}
Debug.LogWarning("无法从流程框架获取当前动作的JudgmentQuestions");
}
catch (System.Exception ex)
{
Debug.LogError($"从流程框架加载答案时发生错误: {ex.Message}");
}
}
/// <summary>
/// 解析JudgmentQuestions并构建答案数组
/// </summary>
private void ParseAnswersFromJudgmentQuestions(List<JudgmentQuestionConfig> questions)
{
// 初始化字典来存储每组的答案
Dictionary<string, Dictionary<int, string>> answerGroups = new Dictionary<string, Dictionary<int, string>>();
answerGroups["物料编码"] = new Dictionary<int, string>();
answerGroups["库存地点"] = new Dictionary<int, string>();
answerGroups["工厂"] = new Dictionary<int, string>();
answerGroups["数量"] = new Dictionary<int, string>();
answerGroups["批次"] = new Dictionary<int, string>();
foreach (var question in questions)
{
if (string.IsNullOrEmpty(question.Question) || string.IsNullOrEmpty(question.CorrectAnswer))
continue;
// 解析Question格式如"物料编码1"、"库存地点2"等
// 提取类型和索引
foreach (var groupKey in answerGroups.Keys)
{
if (question.Question.Contains(groupKey))
{
// 提取数字(索引)
string numberStr = question.Question.Replace(groupKey, "").Trim();
if (int.TryParse(numberStr, out int index))
{
// 索引转换为数组索引1->0, 2->1, 3->2
int arrayIndex = index - 1;
if (arrayIndex >= 0)
{
answerGroups[groupKey][arrayIndex] = question.CorrectAnswer;
}
}
break;
}
}
}
// 为每个类型独立计算其最大索引(允许不同类型有不同的数量)
int GetMaxIndexForGroup(Dictionary<int, string> group)
{
if (group.Count == 0) return 0;
return group.Keys.Max() + 1;
}
// 为每个类型构建对应长度的数组
int materialCodeMaxIndex = GetMaxIndexForGroup(answerGroups["物料编码"]);
int storageLocationMaxIndex = GetMaxIndexForGroup(answerGroups["库存地点"]);
int factoryMaxIndex = GetMaxIndexForGroup(answerGroups["工厂"]);
int quantityMaxIndex = GetMaxIndexForGroup(answerGroups["数量"]);
int batchMaxIndex = GetMaxIndexForGroup(answerGroups["批次"]);
// 构建答案数组(每个类型只创建实际需要的长度)
if (materialCodeMaxIndex > 0)
{
materialCodeAnswers = new string[materialCodeMaxIndex];
for (int i = 0; i < materialCodeMaxIndex; i++)
{
materialCodeAnswers[i] = answerGroups["物料编码"].ContainsKey(i) ? answerGroups["物料编码"][i] : "";
}
}
if (storageLocationMaxIndex > 0)
{
storageLocationAnswers = new string[storageLocationMaxIndex];
for (int i = 0; i < storageLocationMaxIndex; i++)
{
storageLocationAnswers[i] = answerGroups["库存地点"].ContainsKey(i) ? answerGroups["库存地点"][i] : "";
}
}
if (factoryMaxIndex > 0)
{
factoryAnswers = new string[factoryMaxIndex];
for (int i = 0; i < factoryMaxIndex; i++)
{
factoryAnswers[i] = answerGroups["工厂"].ContainsKey(i) ? answerGroups["工厂"][i] : "";
}
}
if (quantityMaxIndex > 0)
{
quantityAnswers = new string[quantityMaxIndex];
for (int i = 0; i < quantityMaxIndex; i++)
{
quantityAnswers[i] = answerGroups["数量"].ContainsKey(i) ? answerGroups["数量"][i] : "";
}
}
if (batchMaxIndex > 0)
{
batchAnswers = new string[batchMaxIndex];
for (int i = 0; i < batchMaxIndex; i++)
{
batchAnswers[i] = answerGroups["批次"].ContainsKey(i) ? answerGroups["批次"][i] : "";
}
}
Debug.Log($"解析答案完成 - 物料编码({materialCodeMaxIndex}个): [{string.Join(", ", materialCodeAnswers)}], " +
$"库存地点({storageLocationMaxIndex}个): [{string.Join(", ", storageLocationAnswers)}], " +
$"工厂({factoryMaxIndex}个): [{string.Join(", ", factoryAnswers)}], " +
$"数量({quantityMaxIndex}个): [{string.Join(", ", quantityAnswers)}], " +
$"批次({batchMaxIndex}个): [{string.Join(", ", batchAnswers)}]");
}
/// <summary>
/// 为所有输入框添加值变化监听
/// </summary>
private void AddInputFieldListeners()
{
// 为物料编码输入框添加监听(带行索引验证)
for (int i = 0; i < materialCodes.Length; i++)
{
int rowIndex = i;
var inputField = materialCodes[i];
if (inputField == null) continue;
inputField.onValueChanged.AddListener((value) =>
{
// 考核模式下不进行实时验证
if (!IsAssessmentMode)
{
ValidateField(materialCodes, materialCodeAnswers, rowIndex, "物料编码");
}
});
}
// 为库存地点输入框添加监听(带行索引验证)
for (int i = 0; i < storageLocations.Length; i++)
{
int rowIndex = i;
var inputField = storageLocations[i];
if (inputField == null) continue;
inputField.onValueChanged.AddListener((value) =>
{
// 考核模式下不进行实时验证
if (!IsAssessmentMode)
{
ValidateField(storageLocations, storageLocationAnswers, rowIndex, "库存地点");
}
});
}
// 为工厂输入框添加监听(带行索引验证)
for (int i = 0; i < factories.Length; i++)
{
int rowIndex = i;
var inputField = factories[i];
if (inputField == null) continue;
inputField.onValueChanged.AddListener((value) =>
{
// 考核模式下不进行实时验证
if (!IsAssessmentMode)
{
ValidateField(factories, factoryAnswers, rowIndex, "工厂");
}
});
}
// 为批次输入框添加监听(带行索引验证)
for (int i = 0; i < batchs.Length; i++)
{
int rowIndex = i;
var inputField = batchs[i];
if (inputField == null) continue;
inputField.onValueChanged.AddListener((value) =>
{
// 考核模式下不进行实时验证
if (!IsAssessmentMode)
{
ValidateField(batchs, batchAnswers, rowIndex, "批次");
}
});
}
// 为数量输入框添加监听(带行索引验证)
for (int i = 0; i < quantities.Length; i++)
{
int rowIndex = i;
var inputField = quantities[i];
if (inputField == null) continue;
inputField.onValueChanged.AddListener((value) =>
{
// 考核模式下不进行实时验证
if (!IsAssessmentMode)
{
ValidateField(quantities, quantityAnswers, rowIndex, "数量");
}
});
}
}
/// <summary>
/// 从答案数组构建 parameters验证成功时使用
/// </summary>
/// <param name="parameters">参数列表(已包含两个下拉框的值)</param>
private void BuildParametersFromAnswers(List<string> parameters)
{
// 添加所有答案:按字段类型分组,每种类型的所有答案连续添加
// 物料编码答案
foreach (var answer in materialCodeAnswers)
{
parameters.Add(answer);
}
// 库存地点答案
foreach (var answer in storageLocationAnswers)
{
parameters.Add(answer);
}
// 工厂答案
foreach (var answer in factoryAnswers)
{
parameters.Add(answer);
}
// 数量答案
foreach (var answer in quantityAnswers)
{
parameters.Add(answer);
}
// 批次答案
foreach (var answer in batchAnswers)
{
parameters.Add(answer);
}
}
/// <summary>
/// 从输入框构建 parameters验证失败或默认使用
/// </summary>
/// <param name="parameters">参数列表(已包含两个下拉框的值)</param>
private void BuildParametersFromInputs(List<string> parameters)
{
// 添加 materialCodes 数组中的文本
foreach (var code in materialCodes)
{
parameters.Add(code.text);
}
// 添加 storageLocations 数组中的文本
foreach (var location in storageLocations)
{
parameters.Add(location.text);
}
// 添加 factories 数组中的文本
foreach (var factory in factories)
{
parameters.Add(factory.text);
}
// 添加 quantities 数组中的文本
foreach (var quantity in quantities)
{
parameters.Add(quantity.text);
}
// 添加 batchs 数组中的文本
foreach (var batch in batchs)
{
parameters.Add(batch.text);
}
}
private void OnQueryButtonClicked()
{
// 所有模式都执行验证,验证成功则使用答案,验证失败则使用输入
bool validationPassed = ValidateAllInputs();
List<string> parameters = new List<string>();
// 添加两个下拉框的选中值
parameters.Add(quantitiesDropdown.options[quantitiesDropdown.value].text);
parameters.Add(quantitiesDropdown2.options[quantitiesDropdown2.value].text);
// 根据验证结果决定使用答案还是用户输入
if (validationPassed)
{
// 验证成功,使用动作的答案填充
BuildParametersFromAnswers(parameters);
}
else
{
// 验证失败,使用输入框的内容填充
BuildParametersFromInputs(parameters);
}
// 更新 stockLocationTransferPostings 和 batchTransferPostings无论验证是否通过都更新
int index = 0;
// 添加 stockLocationTransferPostings 数组中的文本
foreach (var transferPosting in stockLocationTransferPostings)
{
transferPosting.text = storageLocations[index].text;
index++;
}
int index2 = 0;
// 添加 batchTransferPostings 数组中的文本
foreach (var batchTransfer in batchTransferPostings)
{
batchTransfer.text = batchs[index2].text;
index2++;
}
if (MotionEngine.GetModule<ProcessManager>().HandleClick(parameters))
{
TutorialGuideManager.Instance.TriggerNextGuide(jianchaButton.name);
autoHideScript.ShowObject("已检查");
}
}
/// <summary>
/// 验证所有输入是否正确(考核模式使用)
/// </summary>
/// <returns>是否全部输入正确</returns>
private bool ValidateAllInputs()
{
// 清空之前的绑定记录,重新进行匹配
rowToAnswerIndex.Clear();
// 首先尝试通过唯一值确定各行的绑定关系
// 优先使用物料编码、批次、数量这些唯一值来绑定
int maxRows = Math.Max(0, Math.Max(
Math.Max(materialCodes?.Length ?? 0, storageLocations?.Length ?? 0),
Math.Max(factories?.Length ?? 0, Math.Max(batchs?.Length ?? 0, quantities?.Length ?? 0))));
// 第一步:通过唯一值绑定行
for (int rowIndex = 0; rowIndex < maxRows; rowIndex++)
{
// 尝试通过物料编码绑定
if (materialCodes != null && rowIndex < materialCodes.Length && materialCodes[rowIndex] != null)
{
string value = materialCodes[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(value))
{
int bindIndex = FindUniqueIndex(materialCodeAnswers, value);
if (bindIndex >= 0 && !IsAnswerIndexUsed(bindIndex, rowIndex))
{
rowToAnswerIndex[rowIndex] = bindIndex;
continue;
}
}
}
// 尝试通过批次绑定
if (batchs != null && rowIndex < batchs.Length && batchs[rowIndex] != null)
{
string value = batchs[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(value))
{
int bindIndex = FindUniqueIndex(batchAnswers, value);
if (bindIndex >= 0 && !IsAnswerIndexUsed(bindIndex, rowIndex))
{
rowToAnswerIndex[rowIndex] = bindIndex;
continue;
}
}
}
// 尝试通过数量绑定
if (quantities != null && rowIndex < quantities.Length && quantities[rowIndex] != null)
{
string value = quantities[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(value))
{
int bindIndex = FindUniqueIndex(quantityAnswers, value);
if (bindIndex >= 0 && !IsAnswerIndexUsed(bindIndex, rowIndex))
{
rowToAnswerIndex[rowIndex] = bindIndex;
continue;
}
}
}
}
// 第二步:验证所有已绑定行和未绑定行的输入
for (int rowIndex = 0; rowIndex < maxRows; rowIndex++)
{
int answerIndex = -1;
// 如果该行已绑定,使用绑定的答案索引
if (rowToAnswerIndex.ContainsKey(rowIndex))
{
answerIndex = rowToAnswerIndex[rowIndex];
}
else
{
// 未绑定行:尝试通过输入值找到唯一对应的答案组
// 首先尝试通过物料编码查找(通常是最唯一的值)
if (materialCodes != null && rowIndex < materialCodes.Length && materialCodes[rowIndex] != null)
{
string v = materialCodes[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
answerIndex = FindUniqueIndex(materialCodeAnswers, v);
}
}
// 如果物料编码无法唯一确定,尝试其他字段
if (answerIndex < 0 && batchs != null && rowIndex < batchs.Length && batchs[rowIndex] != null)
{
string v = batchs[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
answerIndex = FindUniqueIndex(batchAnswers, v);
}
}
if (answerIndex < 0 && quantities != null && rowIndex < quantities.Length && quantities[rowIndex] != null)
{
string v = quantities[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
answerIndex = FindUniqueIndex(quantityAnswers, v);
}
}
if (answerIndex < 0 && storageLocations != null && rowIndex < storageLocations.Length && storageLocations[rowIndex] != null)
{
string v = storageLocations[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
answerIndex = FindUniqueIndex(storageLocationAnswers, v);
}
}
if (answerIndex < 0 && factories != null && rowIndex < factories.Length && factories[rowIndex] != null)
{
string v = factories[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
answerIndex = FindUniqueIndex(factoryAnswers, v);
}
}
// 如果找到了唯一索引,检查是否已被使用,如果未被使用则进行绑定
if (answerIndex >= 0 && !IsAnswerIndexUsed(answerIndex, rowIndex))
{
rowToAnswerIndex[rowIndex] = answerIndex;
// answerIndex已设置继续后续验证
}
// 如果仍未找到,且没有填写任何字段,跳过该行
else if (answerIndex < 0)
{
// 检查该行是否有任何输入
bool hasAnyInput = false;
if (materialCodes != null && rowIndex < materialCodes.Length && materialCodes[rowIndex] != null && !string.IsNullOrEmpty(materialCodes[rowIndex].text?.Trim()))
hasAnyInput = true;
if (!hasAnyInput && storageLocations != null && rowIndex < storageLocations.Length && storageLocations[rowIndex] != null && !string.IsNullOrEmpty(storageLocations[rowIndex].text?.Trim()))
hasAnyInput = true;
if (!hasAnyInput && factories != null && rowIndex < factories.Length && factories[rowIndex] != null && !string.IsNullOrEmpty(factories[rowIndex].text?.Trim()))
hasAnyInput = true;
if (!hasAnyInput && batchs != null && rowIndex < batchs.Length && batchs[rowIndex] != null && !string.IsNullOrEmpty(batchs[rowIndex].text?.Trim()))
hasAnyInput = true;
if (!hasAnyInput && quantities != null && rowIndex < quantities.Length && quantities[rowIndex] != null && !string.IsNullOrEmpty(quantities[rowIndex].text?.Trim()))
hasAnyInput = true;
// 如果该行有输入但无法绑定到答案组,说明输入错误
if (hasAnyInput)
{
Debug.LogWarning($"第{rowIndex + 1}行有输入但无法唯一确定对应的答案组");
return false;
}
// 如果该行没有任何输入,跳过验证
continue;
}
else if (answerIndex >= 0 && IsAnswerIndexUsed(answerIndex, rowIndex))
{
// 答案组已被其他行使用,说明输入错误
Debug.LogWarning($"第{rowIndex + 1}行的答案组已被其他行使用");
return false;
}
}
// 确保answerIndex有效
if (answerIndex < 0) continue;
// 验证该行的所有字段是否匹配绑定的答案组
// 物料编码
if (materialCodes != null && rowIndex < materialCodes.Length && materialCodes[rowIndex] != null)
{
string v = materialCodes[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
if (answerIndex >= materialCodeAnswers.Length || !string.Equals(v, materialCodeAnswers[answerIndex], StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"第{rowIndex + 1}行物料编码应为:{materialCodeAnswers[answerIndex]},实际输入:{v}");
return false;
}
}
}
// 库存地点
if (storageLocations != null && rowIndex < storageLocations.Length && storageLocations[rowIndex] != null)
{
string v = storageLocations[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
if (answerIndex >= storageLocationAnswers.Length || !string.Equals(v, storageLocationAnswers[answerIndex], StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"第{rowIndex + 1}行库存地点应为:{storageLocationAnswers[answerIndex]},实际输入:{v}");
return false;
}
}
}
// 工厂
if (factories != null && rowIndex < factories.Length && factories[rowIndex] != null)
{
string v = factories[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
if (answerIndex >= factoryAnswers.Length || !string.Equals(v, factoryAnswers[answerIndex], StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"第{rowIndex + 1}行工厂应为:{factoryAnswers[answerIndex]},实际输入:{v}");
return false;
}
}
}
// 批次
if (batchs != null && rowIndex < batchs.Length && batchs[rowIndex] != null)
{
string v = batchs[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
if (answerIndex >= batchAnswers.Length || !string.Equals(v, batchAnswers[answerIndex], StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"第{rowIndex + 1}行批次应为:{batchAnswers[answerIndex]},实际输入:{v}");
return false;
}
}
}
// 数量
if (quantities != null && rowIndex < quantities.Length && quantities[rowIndex] != null)
{
string v = quantities[rowIndex].text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(v))
{
if (answerIndex >= quantityAnswers.Length || !string.Equals(v, quantityAnswers[answerIndex], StringComparison.OrdinalIgnoreCase))
{
Debug.LogWarning($"第{rowIndex + 1}行数量应为:{quantityAnswers[answerIndex]},实际输入:{v}");
return false;
}
}
}
}
// 检查是否所有答案组都被使用(确保没有遗漏)
// 找到实际的最大答案组数量(不同字段可能有不同的数量,取最大值)
int maxAnswerCount = Math.Max(0, Math.Max(
Math.Max(materialCodeAnswers.Length, storageLocationAnswers.Length),
Math.Max(factoryAnswers.Length, Math.Max(quantityAnswers.Length, batchAnswers.Length))));
HashSet<int> usedAnswerIndices = new HashSet<int>();
foreach (var kvp in rowToAnswerIndex)
{
usedAnswerIndices.Add(kvp.Value);
}
// 确保所有答案组都被使用(根据实际答案组数量判断)
// 例如如果只有批次1、批次22组那么必须使用2组如果有3组必须使用3组
if (maxAnswerCount > 0 && usedAnswerIndices.Count < maxAnswerCount)
{
Debug.Log($"验证失败:使用了{usedAnswerIndices.Count}组答案,但应该有{maxAnswerCount}组答案");
return false;
}
return true;
}
/// <summary>
/// 按行校验:判断输入是否正确,只判断不清空,并打印验证结果
/// </summary>
private void ValidateField(TMP_InputField[] fields, string[] answers, int rowIndex, string label)
{
if (fields == null || answers == null) return;
if (rowIndex < 0 || rowIndex >= fields.Length) return;
var field = fields[rowIndex];
if (field == null) return;
// 检查该字段是否已完成验证,如果已完成则不再跟踪
var fieldKey = (rowIndex, label);
if (completedFields.Contains(fieldKey))
{
return; // 已完成验证,不再跟踪该字段的变化
}
string value = field.text == null ? string.Empty : field.text.Trim();
if (string.IsNullOrEmpty(value))
{
// 输入为空时,重置该字段的验证状态(但不移除已完成标记,因为可能用户误删)
var stateKey = (rowIndex, label);
if (fieldValidationStates.ContainsKey(stateKey))
{
fieldValidationStates[stateKey] = false;
}
return;
}
// 获取当前字段的验证状态
var validationKey = (rowIndex, label);
bool wasValidated = fieldValidationStates.ContainsKey(validationKey) && fieldValidationStates[validationKey];
// 未绑定行:
if (!rowToAnswerIndex.ContainsKey(rowIndex))
{
int bindIndex = FindUniqueIndex(answers, value);
if (bindIndex >= 0)
{
// 检查该答案组是否已被其他行使用
if (IsAnswerIndexUsed(bindIndex, rowIndex))
{
// 答案组已被使用,只判断不清空
Debug.Log("不对");
// 重置验证状态
fieldValidationStates[validationKey] = false;
return;
}
// 可以唯一确定答案组,进行绑定
AssignRowAnswerIndex(rowIndex, bindIndex);
Debug.Log("对");
// 只有当之前未验证通过时,才触发向导(避免重复触发)
if (!wasValidated)
{
TutorialGuideManager.Instance.TriggerNextGuide();
fieldValidationStates[validationKey] = true;
// 标记该字段为已完成验证,后续不再跟踪
completedFields.Add(validationKey);
}
// 验证该行其他字段
CheckOtherFieldsConsistency(rowIndex);
return;
}
// 无法唯一确定(例如 HMA1/01M0 等重复值):
// 检查是否存在于答案集合中
if (ExistsInAnswers(answers, value))
{
// 值存在于答案集合中,但无法确定是哪个组,暂时允许(等待绑定)
Debug.Log("对");
// 只有当之前未验证通过时,才触发向导(避免重复触发)
if (!wasValidated)
{
TutorialGuideManager.Instance.TriggerNextGuide();
fieldValidationStates[validationKey] = true;
// 标记该字段为已完成验证,后续不再跟踪
completedFields.Add(validationKey);
}
}
else
{
// 该值在该字段的答案集合中完全不存在
Debug.Log("不对");
// 重置验证状态
fieldValidationStates[validationKey] = false;
}
return;
}
// 已绑定行:所有字段必须匹配绑定的答案组
int expectedIndex = rowToAnswerIndex[rowIndex];
if (expectedIndex < 0 || expectedIndex >= answers.Length) return;
string expected = answers[expectedIndex];
if (string.Equals(value, expected, StringComparison.OrdinalIgnoreCase))
{
// 输入正确
Debug.Log("对");
// 只有当之前未验证通过时,才触发向导(避免重复触发)
if (!wasValidated)
{
TutorialGuideManager.Instance.TriggerNextGuide();
fieldValidationStates[validationKey] = true;
// 标记该字段为已完成验证,后续不再跟踪
completedFields.Add(validationKey);
}
}
else
{
// 输入错误,只判断不清空
Debug.Log("不对");
// 重置验证状态(当输入变为不正确时,下次再次输入正确时才会触发向导)
fieldValidationStates[validationKey] = false;
}
}
/// <summary>
/// 在某字段答案数组中查找值的唯一索引(仅当且仅当出现一次时返回索引,否则返回-1
/// </summary>
private int FindUniqueIndex(string[] answers, string value)
{
if (answers == null || answers.Length == 0) return -1;
if (string.IsNullOrEmpty(value)) return -1;
int found = -1;
for (int i = 0; i < answers.Length; i++)
{
// 跳过空字符串(表示该索引位置没有答案配置)
if (string.IsNullOrEmpty(answers[i])) continue;
if (string.Equals(answers[i], value, StringComparison.OrdinalIgnoreCase))
{
if (found != -1) return -1; // 出现两次及以上:不唯一
found = i;
}
}
return found;
}
/// <summary>
/// 值是否存在于该字段的答案集合中
/// </summary>
private bool ExistsInAnswers(string[] answers, string value)
{
if (answers == null || answers.Length == 0) return false;
if (string.IsNullOrEmpty(value)) return false;
for (int i = 0; i < answers.Length; i++)
{
// 跳过空字符串(表示该索引位置没有答案配置)
if (string.IsNullOrEmpty(answers[i])) continue;
if (string.Equals(answers[i], value, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
/// <summary>
/// 检查答案组索引是否已被其他行使用
/// </summary>
/// <param name="answerIndex">答案组索引</param>
/// <param name="excludeRowIndex">排除的行索引(当前正在处理的行)</param>
/// <returns>是否已被使用</returns>
private bool IsAnswerIndexUsed(int answerIndex, int excludeRowIndex)
{
foreach (var kvp in rowToAnswerIndex)
{
// 如果其他行已经绑定了该答案组则返回true
if (kvp.Key != excludeRowIndex && kvp.Value == answerIndex)
{
return true;
}
}
return false;
}
/// <summary>
/// 绑定某一行对应的答案组
/// </summary>
private void AssignRowAnswerIndex(int rowIndex, int answerIndex)
{
rowToAnswerIndex[rowIndex] = answerIndex;
Debug.Log($"无序绑定:第{rowIndex + 1}行绑定到第{answerIndex + 1}组");
}
/// <summary>
/// 检查已绑定行的其他字段一致性:只判断不清空
/// </summary>
private void CheckOtherFieldsConsistency(int rowIndex)
{
if (!rowToAnswerIndex.ContainsKey(rowIndex)) return;
int idx = rowToAnswerIndex[rowIndex];
void check(TMP_InputField[] fields, string[] answers, string label)
{
if (fields == null || answers == null) return;
if (rowIndex < 0 || rowIndex >= fields.Length) return;
if (idx < 0 || idx >= answers.Length) return;
var f = fields[rowIndex];
if (f == null) return;
string v = f.text == null ? string.Empty : f.text.Trim();
if (string.IsNullOrEmpty(v)) return;
string expected = answers[idx];
if (string.Equals(v, expected, StringComparison.OrdinalIgnoreCase))
{
// 输入正确
Debug.Log("对");
}
else
{
// 输入错误,只判断不清空
Debug.Log("不对");
}
}
check(materialCodes, materialCodeAnswers, "物料编码");
check(storageLocations, storageLocationAnswers, "库存地点");
check(factories, factoryAnswers, "工厂");
check(batchs, batchAnswers, "批次");
check(quantities, quantityAnswers, "数量");
}
}
}