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.库存物资库存地点批量切换
{
///
/// m过账转移new
///
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 rowToAnswerIndex = new Dictionary();
// 字段验证状态跟踪:记录每个字段是否已经通过验证并触发过向导(用于避免重复触发)
// 键:(行索引, 字段标签),值:是否已经触发过向导
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; // 缓存的当前模式
///
/// 获取当前是否为考核模式
///
private bool IsAssessmentMode => _cachedCurrentMode == ProcessMode.考核模式;
private void Start()
{
// 缓存当前模式
CacheCurrentMode();
// 初始化输入验证系统
InitializeInputValidation();
jianchaButton.onClick.AddListener(OnQueryButtonClicked);
queryButton.onClick.AddListener(delegate
{
if (MotionEngine.GetModule().HandleClick("过账"))
{
autoHideScript.ShowObject("已过账");
TutorialGuideManager.Instance.TriggerNextGuide(queryButton.name);
}
});
}
///
/// 缓存当前模式
///
private void CacheCurrentMode()
{
try
{
var processManager = MotionEngine.GetModule();
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.教学模式; // 默认模式
}
}
///
/// 初始化输入验证系统
///
private void InitializeInputValidation()
{
// 从流程框架获取答案并解析
LoadAnswersFromFramework();
// 为所有输入框添加值变化监听
AddInputFieldListeners();
}
///
/// 从流程框架获取当前动作的答案并解析
///
private void LoadAnswersFromFramework()
{
try
{
var processManager = MotionEngine.GetModule();
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}");
}
}
///
/// 解析JudgmentQuestions并构建答案数组
///
private void ParseAnswersFromJudgmentQuestions(List questions)
{
// 初始化字典来存储每组的答案
Dictionary> answerGroups = new Dictionary>();
answerGroups["物料编码"] = new Dictionary();
answerGroups["库存地点"] = new Dictionary();
answerGroups["工厂"] = new Dictionary();
answerGroups["数量"] = new Dictionary();
answerGroups["批次"] = new Dictionary();
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 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)}]");
}
///
/// 为所有输入框添加值变化监听
///
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, "数量");
}
});
}
}
///
/// 从答案数组构建 parameters(验证成功时使用)
///
/// 参数列表(已包含两个下拉框的值)
private void BuildParametersFromAnswers(List 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);
}
}
///
/// 从输入框构建 parameters(验证失败或默认使用)
///
/// 参数列表(已包含两个下拉框的值)
private void BuildParametersFromInputs(List 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 parameters = new List();
// 添加两个下拉框的选中值
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().HandleClick(parameters))
{
TutorialGuideManager.Instance.TriggerNextGuide(jianchaButton.name);
autoHideScript.ShowObject("已检查");
}
}
///
/// 验证所有输入是否正确(考核模式使用)
///
/// 是否全部输入正确
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 usedAnswerIndices = new HashSet();
foreach (var kvp in rowToAnswerIndex)
{
usedAnswerIndices.Add(kvp.Value);
}
// 确保所有答案组都被使用(根据实际答案组数量判断)
// 例如:如果只有批次1、批次2(2组),那么必须使用2组;如果有3组,必须使用3组
if (maxAnswerCount > 0 && usedAnswerIndices.Count < maxAnswerCount)
{
Debug.Log($"验证失败:使用了{usedAnswerIndices.Count}组答案,但应该有{maxAnswerCount}组答案");
return false;
}
return true;
}
///
/// 按行校验:判断输入是否正确,只判断不清空,并打印验证结果
///
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;
}
}
///
/// 在某字段答案数组中查找值的唯一索引(仅当且仅当出现一次时返回索引,否则返回-1)
///
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;
}
///
/// 值是否存在于该字段的答案集合中
///
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;
}
///
/// 检查答案组索引是否已被其他行使用
///
/// 答案组索引
/// 排除的行索引(当前正在处理的行)
/// 是否已被使用
private bool IsAnswerIndexUsed(int answerIndex, int excludeRowIndex)
{
foreach (var kvp in rowToAnswerIndex)
{
// 如果其他行已经绑定了该答案组,则返回true
if (kvp.Key != excludeRowIndex && kvp.Value == answerIndex)
{
return true;
}
}
return false;
}
///
/// 绑定某一行对应的答案组
///
private void AssignRowAnswerIndex(int rowIndex, int answerIndex)
{
rowToAnswerIndex[rowIndex] = answerIndex;
Debug.Log($"无序绑定:第{rowIndex + 1}行绑定到第{answerIndex + 1}组");
}
///
/// 检查已绑定行的其他字段一致性:只判断不清空
///
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, "数量");
}
}
}