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; using ProcessMode = DefaultNamespace.ProcessMode.ProcessMode; namespace Zion.Scripts.ERP.库存物资查询报表 { public class LedgerTransferPostingManager : MonoBehaviour { public TMP_InputField[] batchs; //批次输入框数组 public TMP_InputField[] materialCodes; // 物料编码输入框数组 public TMP_InputField[] storageLocations; // 库存地点输入框数组 public TMP_InputField[] factories; // 工厂输入框数组 public TMP_InputField[] movementReasons; // 移动原因输入框数组 public TMP_InputField[] quantities; // 数量输入框数组 public TMP_InputField[] kucundidian; // 数量输入框数组 public TMP_InputField[] picichuanshu; // 数量输入框数组 public TMP_Dropdown quantitiesDropdown; public TMP_Dropdown quantitiesDropdown2; public Button queryButton; public Button jianchaButton; public TMP_Text titleText; public bool onlyFirst = false; public AutoHideScript autoHideScript; // 输入顺序验证相关变量 private bool isMaterialCodeCompleted = false; // 物料编码是否完成 private bool isStorageLocationCompleted = false; // 库存地点是否完成 private bool isFactoryCompleted = false; // 工厂是否完成 private bool isInputOrderValid = true; // 输入顺序是否有效 // 模式管理相关变量 private ProcessMode _cachedCurrentMode; // 缓存的当前模式 // 每行标准答案(索引0对应"1行/1号")- 从框架动态获取 private string[] materialCodeAnswers = new string[0]; private string[] storageLocationAnswers = new string[0]; private string[] factoryAnswers = new string[0]; private string[] movementReasonAnswers = 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 bool IsTeachingMode => _cachedCurrentMode == ProcessMode.教学模式; /// /// 获取当前是否为考核模式 /// private bool IsAssessmentMode => _cachedCurrentMode == ProcessMode.考核模式; private void Start() { queryButton.onClick.AddListener(() => OnQueryButtonClicked()); jianchaButton.onClick.AddListener(delegate { for (int i = 0; i < storageLocations.Length; i++) { kucundidian[i].text = storageLocations[i].text; } for (int i = 0; i < batchs.Length; i++) { picichuanshu[i].text = batchs[i].text; } autoHideScript.ShowObject("已检查"); TutorialGuideManager.Instance.TriggerNextGuide(jianchaButton.name); }); // 初始化输入验证系统 InitializeInputValidation(); } /// /// 初始化下拉框默认选择 /// private void InitializeDropdowns() { // 初始化quantitiesDropdown默认选择第一个选项 if (quantitiesDropdown != null && quantitiesDropdown.options.Count > 0) { quantitiesDropdown.value = 0; Debug.Log($"quantitiesDropdown默认选择: {quantitiesDropdown.options[0].text}"); } else { Debug.LogWarning("quantitiesDropdown为null或没有选项"); } // 初始化quantitiesDropdown2默认选择第一个选项 if (quantitiesDropdown2 != null && quantitiesDropdown2.options.Count > 0) { quantitiesDropdown2.value = 0; Debug.Log($"quantitiesDropdown2默认选择: {quantitiesDropdown2.options[0].text}"); } else { Debug.LogWarning("quantitiesDropdown2为null或没有选项"); } } /// /// 初始化输入验证系统 /// private void InitializeInputValidation() { // 缓存当前模式 CacheCurrentMode(); // 从流程框架获取答案并解析 LoadAnswersFromFramework(); // 为所有输入框添加值变化监听 AddInputFieldListeners(); // 初始化输入框状态 UpdateInputFieldStates(); } /// /// 从流程框架获取当前动作的答案并解析 /// 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(); 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 movementReasonMaxIndex = 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 (movementReasonMaxIndex > 0) { movementReasonAnswers = new string[movementReasonMaxIndex]; for (int i = 0; i < movementReasonMaxIndex; i++) { movementReasonAnswers[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)}], " + $"移动原因({movementReasonMaxIndex}个): [{string.Join(", ", movementReasonAnswers)}], " + $"数量({quantityMaxIndex}个): [{string.Join(", ", quantityAnswers)}], " + $"批次({batchMaxIndex}个): [{string.Join(", ", batchAnswers)}]"); } /// /// 缓存当前模式 /// 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 AddInputFieldListeners() { // 为物料编码输入框添加监听(带行索引验证) for (int i = 0; i < materialCodes.Length; i++) { int rowIndex = i; var inputField = materialCodes[i]; if (inputField == null) continue; inputField.onValueChanged.AddListener((value) => { OnMaterialCodeChanged(value); 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) => { OnStorageLocationChanged(value); 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) => { OnFactoryChanged(value); 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) => { OnBatchChanged(value); if (isInputOrderValid) { ValidateField(batchs, batchAnswers, rowIndex, "批次"); } }); // 在教学模式下添加焦点控制 if (IsTeachingMode) { inputField.onSelect.AddListener((value) => OnBatchFieldSelected(inputField)); } } // 为移动原因输入框添加监听(带行索引验证) for (int i = 0; i < movementReasons.Length; i++) { int rowIndex = i; var inputField = movementReasons[i]; if (inputField == null) continue; inputField.onValueChanged.AddListener((value) => { OnMovementReasonChanged(value); ValidateField(movementReasons, movementReasonAnswers, rowIndex, "移动原因"); }); } // 为数量输入框添加监听(带行索引验证) for (int i = 0; i < quantities.Length; i++) { int rowIndex = i; var inputField = quantities[i]; if (inputField == null) continue; inputField.onValueChanged.AddListener((value) => { OnQuantityChanged(value); ValidateField(quantities, quantityAnswers, rowIndex, "数量"); }); } } /// /// 按行校验:判断输入是否正确,只判断不清空,并打印验证结果 /// 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)) { // 答案组已被使用,只判断不清空 //ShowInputOrderError($"第{bindIndex + 1}组答案已被其他行使用,不能重复绑定"); 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/3 等重复值): // 检查是否存在于答案集合中 if (ExistsInAnswers(answers, value)) { // 值存在于答案集合中,但无法确定是哪个组,暂时允许(等待绑定) Debug.Log("对"); // 只有当之前未验证通过时,才触发向导(避免重复触发) if (!wasValidated) { TutorialGuideManager.Instance.TriggerNextGuide(); fieldValidationStates[validationKey] = true; // 标记该字段为已完成验证,后续不再跟踪 completedFields.Add(validationKey); } } else { // 该值在该字段的答案集合中完全不存在 //ShowInputOrderError($"第{rowIndex + 1}行{label}输入不在允许范围内"); 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 { // 输入错误,只判断不清空 //ShowInputOrderError($"第{rowIndex + 1}行已绑定到第{expectedIndex + 1}组,{label}应为:{expected}"); 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 { // 输入错误,只判断不清空 // ShowInputOrderError($"第{rowIndex + 1}行已绑定到第{idx + 1}组,{label}应为:{expected}"); Debug.Log("不对"); } } check(materialCodes, materialCodeAnswers, "物料编码"); check(storageLocations, storageLocationAnswers, "库存地点"); check(factories, factoryAnswers, "工厂"); check(batchs, batchAnswers, "批次"); check(movementReasons, movementReasonAnswers, "移动原因"); check(quantities, quantityAnswers, "数量"); } /// /// 更新输入框状态,根据前置条件启用/禁用输入框 /// private void UpdateInputFieldStates() { // 物料编码输入框始终可用 SetInputFieldsInteractable(materialCodes, true); // 库存地点输入框:允许用户开始输入流程,不要求物料编码完全完成 SetInputFieldsInteractable(storageLocations, true); // 工厂输入框:允许用户开始输入流程,不要求库存地点完全完成 SetInputFieldsInteractable(factories, true); // 批次输入框:在教学模式下,只有在物料编码、库存地点、工厂都完成后才可用 bool canInputBatch = true; // 默认允许输入 if (IsTeachingMode) { canInputBatch = isMaterialCodeCompleted && isStorageLocationCompleted && isFactoryCompleted; } // 调试日志:检查批次输入框数组状态 Debug.Log($"批次输入框控制 - 教学模式: {IsTeachingMode}, 可输入: {canInputBatch}, 批次输入框数量: {batchs?.Length ?? 0}"); SetInputFieldsInteractable(batchs, canInputBatch, true); // 批次输入框需要特殊处理 // 移动原因输入框:始终可用 SetInputFieldsInteractable(movementReasons, true); // 数量输入框:始终可用 SetInputFieldsInteractable(quantities, true); } /// /// 设置输入框数组的交互状态 /// /// 输入框数组 /// 是否可交互 /// 是否为批次输入框(需要特殊处理) private void SetInputFieldsInteractable(TMP_InputField[] inputFields, bool interactable, bool isBatchField = false) { if (inputFields == null) { Debug.LogWarning("输入框数组为null"); return; } int processedCount = 0; if (!onlyFirst) { foreach (var inputField in inputFields) { if (inputField != null) { inputField.interactable = interactable; processedCount++; // 在教学模式下,对批次输入框进行特殊处理 if (IsTeachingMode && isBatchField) { // 使用readOnly属性完全阻止输入 inputField.readOnly = !interactable; } else if (isBatchField) { // 非教学模式下,批次输入框正常可用 inputField.readOnly = false; } // 添加视觉反馈:禁用时变灰 var colors = inputField.colors; // colors.normalColor = interactable ? Color.white : Color.gray; inputField.colors = colors; } } } else { TMP_InputField inputField = inputFields[0]; if (inputField != null) { inputField.interactable = interactable; processedCount++; // 在教学模式下,对批次输入框进行特殊处理 if (IsTeachingMode && isBatchField) { // 使用readOnly属性完全阻止输入 inputField.readOnly = !interactable; } else if (isBatchField) { // 非教学模式下,批次输入框正常可用 inputField.readOnly = false; } // 添加视觉反馈:禁用时变灰 var colors = inputField.colors; // colors.normalColor = interactable ? Color.white : Color.gray; inputField.colors = colors; } } // 调试日志:显示处理的输入框数量 if (isBatchField) { Debug.Log($"批次输入框处理完成 - 总数: {inputFields.Length}, 已处理: {processedCount}, 可交互: {interactable}"); } } /// /// 检查输入框数组中是否有任何输入框被填写 /// /// 输入框数组 /// 是否有输入框被填写 private bool IsAnyInputFieldFilled(TMP_InputField[] inputFields) { foreach (var inputField in inputFields) { if (inputField != null && !string.IsNullOrEmpty(inputField.text.Trim())) { return true; } } return false; } /// /// 智能检查输入框是否完成(根据onlyFirst决定检查范围) /// /// 输入框数组 /// 输入框是否完成 private bool IsInputFieldCompleted(TMP_InputField[] inputFields) { if (inputFields == null || inputFields.Length == 0) { return false; } if (onlyFirst) { // 只检查第一个输入框 return inputFields[0] != null && !string.IsNullOrEmpty(inputFields[0].text.Trim()); } else { // 检查所有输入框 return IsAnyInputFieldFilled(inputFields); } } /// /// 物料编码输入变化事件处理 /// /// 输入值 private void OnMaterialCodeChanged(string value) { // 检查物料编码是否完成(根据onlyFirst决定检查范围) isMaterialCodeCompleted = IsInputFieldCompleted(materialCodes); // 更新输入框状态 UpdateInputFieldStates(); Debug.Log($"物料编码状态更新: {isMaterialCodeCompleted} (onlyFirst: {onlyFirst})"); } /// /// 库存地点输入变化事件处理 /// /// 输入值 private void OnStorageLocationChanged(string value) { // 检查库存地点是否完成(根据onlyFirst决定检查范围) isStorageLocationCompleted = IsInputFieldCompleted(storageLocations); // 更新输入框状态 UpdateInputFieldStates(); Debug.Log($"库存地点状态更新: {isStorageLocationCompleted} (onlyFirst: {onlyFirst})"); } /// /// 工厂输入变化事件处理 /// /// 输入值 private void OnFactoryChanged(string value) { // 检查工厂是否完成(根据onlyFirst决定检查范围) isFactoryCompleted = IsInputFieldCompleted(factories); // 更新输入框状态 UpdateInputFieldStates(); Debug.Log($"工厂状态更新: {isFactoryCompleted} (onlyFirst: {onlyFirst})"); } /// /// 批次输入变化事件处理 /// /// 输入值 private void OnBatchChanged(string value) { // 验证输入顺序:只有在物料编码、库存地点、工厂都完成后才能输入批次 if (!string.IsNullOrEmpty(value.Trim()) && !(isMaterialCodeCompleted && isStorageLocationCompleted && isFactoryCompleted)) { // 输入顺序错误,清空批次输入并提示 foreach (var batch in batchs) { if (batch.text == value) { batch.text = ""; break; } } // 显示错误提示 //ShowInputOrderError("请先完成物料编码、库存地点、工厂的输入,再输入批次号"); isInputOrderValid = false; return; } isInputOrderValid = true; // 更新输入框状态 UpdateInputFieldStates(); Debug.Log($"批次输入验证: 顺序有效 = {isInputOrderValid}"); } /// /// 批次输入框获得焦点事件处理(教学模式专用) /// /// 获得焦点的输入框 private void OnBatchFieldSelected(TMP_InputField inputField) { // 检查前置条件是否满足 if (!(isMaterialCodeCompleted && isStorageLocationCompleted && isFactoryCompleted)) { // 前置条件不满足,立即取消焦点 inputField.DeactivateInputField(); // 显示提示信息 //ShowInputOrderError("请先完成物料编码、库存地点、工厂的输入,再输入批次号"); Debug.Log("教学模式:批次输入框焦点被阻止,前置条件未满足"); } } /// /// 移动原因输入变化事件处理 /// /// 输入值 private void OnMovementReasonChanged(string value) { // 更新输入框状态 UpdateInputFieldStates(); } /// /// 数量输入变化事件处理 /// /// 输入值 private void OnQuantityChanged(string value) { // 更新输入框状态 UpdateInputFieldStates(); } // /// // /// 显示输入顺序错误提示 // /// // /// 错误消息 // private void ShowInputOrderError(string message) // { // // 使用现有的AutoHideScript显示错误提示 // if (autoHideScript != null) // { // autoHideScript.ShowObject(message); // } // // Debug.LogWarning($"输入顺序错误: {message}"); // } /// /// 提交前整体校验:若某行任一字段填写,则该字段必须等于该行绑定的标准答案 /// private bool ValidateAllRows() { int rowCount = Math.Max(0, Math.Max( Math.Max(materialCodes?.Length ?? 0, storageLocations?.Length ?? 0), Math.Max(factories?.Length ?? 0, Math.Max(batchs?.Length ?? 0, Math.Max(movementReasons?.Length ?? 0, quantities?.Length ?? 0))))); for (int i = 0; i < rowCount; i++) { // 获取该行绑定的答案组索引(如果已绑定) int answerIndex = -1; if (rowToAnswerIndex.ContainsKey(i)) { answerIndex = rowToAnswerIndex[i]; } else { // 未绑定的行:尝试通过输入值找到唯一对应的答案组 // 首先尝试通过物料编码查找(通常是最唯一的值) if (materialCodes != null && i < materialCodes.Length && materialCodes[i] != null) { string v = materialCodes[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { answerIndex = FindUniqueIndex(materialCodeAnswers, v); } } // 如果物料编码无法唯一确定,尝试其他字段 if (answerIndex < 0 && storageLocations != null && i < storageLocations.Length && storageLocations[i] != null) { string v = storageLocations[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { answerIndex = FindUniqueIndex(storageLocationAnswers, v); } } // 如果仍未找到,且没有填写任何字段,跳过该行 if (answerIndex < 0) { // 检查该行是否有任何输入 bool hasAnyInput = false; if (materialCodes != null && i < materialCodes.Length && materialCodes[i] != null && !string.IsNullOrEmpty(materialCodes[i].text?.Trim())) hasAnyInput = true; if (!hasAnyInput && storageLocations != null && i < storageLocations.Length && storageLocations[i] != null && !string.IsNullOrEmpty(storageLocations[i].text?.Trim())) hasAnyInput = true; if (!hasAnyInput && factories != null && i < factories.Length && factories[i] != null && !string.IsNullOrEmpty(factories[i].text?.Trim())) hasAnyInput = true; if (!hasAnyInput && batchs != null && i < batchs.Length && batchs[i] != null && !string.IsNullOrEmpty(batchs[i].text?.Trim())) hasAnyInput = true; if (!hasAnyInput && movementReasons != null && i < movementReasons.Length && movementReasons[i] != null && !string.IsNullOrEmpty(movementReasons[i].text?.Trim())) hasAnyInput = true; if (!hasAnyInput && quantities != null && i < quantities.Length && quantities[i] != null && !string.IsNullOrEmpty(quantities[i].text?.Trim())) hasAnyInput = true; // 如果该行有输入但无法绑定到答案组,说明输入错误 if (hasAnyInput) { Debug.LogWarning($"第{i + 1}行有输入但无法唯一确定对应的答案组"); return false; } // 如果该行没有任何输入,跳过验证 continue; } } // 使用绑定的答案组索引验证该行的所有字段(如果answerIndex无效则跳过) if (answerIndex < 0) continue; // 验证物料编码 if (materialCodes != null && i < materialCodes.Length && materialCodes[i] != null) { string v = materialCodes[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= materialCodeAnswers.Length || !string.Equals(v, materialCodeAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行物料编码应为:{materialCodeAnswers[answerIndex]},实际输入:{v}"); return false; } } } // 验证库存地点 if (storageLocations != null && i < storageLocations.Length && storageLocations[i] != null) { string v = storageLocations[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= storageLocationAnswers.Length || !string.Equals(v, storageLocationAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行库存地点应为:{storageLocationAnswers[answerIndex]},实际输入:{v}"); return false; } } } // 验证工厂 if (factories != null && i < factories.Length && factories[i] != null) { string v = factories[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= factoryAnswers.Length || !string.Equals(v, factoryAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行工厂应为:{factoryAnswers[answerIndex]},实际输入:{v}"); return false; } } } // 验证批次 if (batchs != null && i < batchs.Length && batchs[i] != null) { string v = batchs[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= batchAnswers.Length || !string.Equals(v, batchAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行批次应为:{batchAnswers[answerIndex]},实际输入:{v}"); return false; } } } // 验证移动原因 if (movementReasons != null && i < movementReasons.Length && movementReasons[i] != null) { string v = movementReasons[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= movementReasonAnswers.Length || !string.Equals(v, movementReasonAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行移动原因应为:{movementReasonAnswers[answerIndex]},实际输入:{v}"); return false; } } } // 验证数量 if (quantities != null && i < quantities.Length && quantities[i] != null) { string v = quantities[i].text?.Trim() ?? string.Empty; if (!string.IsNullOrEmpty(v)) { if (answerIndex >= quantityAnswers.Length || !string.Equals(v, quantityAnswers[answerIndex], StringComparison.OrdinalIgnoreCase)) { Debug.LogWarning($"第{i + 1}行数量应为:{quantityAnswers[answerIndex]},实际输入:{v}"); return false; } } } } return true; } /// /// 验证输入顺序是否有效 /// /// 输入顺序是否有效 private bool ValidateInputOrder() { // 检查是否所有必需的字段都按顺序完成 bool materialCodeValid = isMaterialCodeCompleted; bool storageLocationValid = isStorageLocationCompleted && materialCodeValid; bool factoryValid = isFactoryCompleted && storageLocationValid; bool batchValid = IsAnyInputFieldFilled(batchs) && factoryValid; bool movementReasonValid = IsAnyInputFieldFilled(movementReasons) && batchValid; bool quantityValid = IsAnyInputFieldFilled(quantities) && movementReasonValid; return materialCodeValid && storageLocationValid && factoryValid && batchValid && movementReasonValid && quantityValid; } /// /// 从答案数组构建 parameters(验证成功时使用) /// /// 参数列表(已包含两个下拉框的值) private void BuildParametersFromAnswers(List parameters) { if (onlyFirst) { // 只添加每个答案数组的第一个答案 if (materialCodeAnswers.Length > 0) parameters.Add(materialCodeAnswers[0]); if (storageLocationAnswers.Length > 0) parameters.Add(storageLocationAnswers[0]); if (factoryAnswers.Length > 0) parameters.Add(factoryAnswers[0]); if (movementReasonAnswers.Length > 0) parameters.Add(movementReasonAnswers[0]); if (quantityAnswers.Length > 0) parameters.Add(quantityAnswers[0]); if (batchAnswers.Length > 0) parameters.Add(batchAnswers[0]); } else { // 添加所有答案:按字段类型分组,每种类型的所有答案连续添加 // 物料编码答案 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 movementReasonAnswers) { 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) { if (onlyFirst) { // 只添加每个数组的第一个输入框的值 if (materialCodes.Length > 0) parameters.Add(materialCodes[0].text); if (storageLocations.Length > 0) parameters.Add(storageLocations[0].text); if (factories.Length > 0) parameters.Add(factories[0].text); if (movementReasons.Length > 0) parameters.Add(movementReasons[0].text); if (quantities.Length > 0) parameters.Add(quantities[0].text); if (batchs.Length > 0) parameters.Add(batchs[0].text); } else { foreach (var code in materialCodes) { parameters.Add(code.text); } foreach (var location in storageLocations) { parameters.Add(location.text); } foreach (var factory in factories) { parameters.Add(factory.text); } foreach (var reason in movementReasons) { parameters.Add(reason.text); } foreach (var quantity in quantities) { parameters.Add(quantity.text); } // 添加所有输入框的值 foreach (var batch in batchs) { parameters.Add(batch.text); } } } private void OnQueryButtonClicked() { // 所有模式都执行验证,验证成功则使用答案,验证失败则使用输入 bool validationPassed = false; // 首先验证输入顺序 if (ValidateInputOrder()) { // 输入顺序验证通过,再进行整体验证 if (ValidateAllRows()) { // 验证完全通过,使用答案填充 validationPassed = true; } } 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); } if (MotionEngine.GetModule().HandleClick(parameters)) { // 保存提交前的onlyFirst状态 bool wasOnlyFirst = onlyFirst; onlyFirst = true; Debug.Log("查询成功"); titleText.text = "物料凭证 500004650 已过账"; autoHideScript.ShowObject("已过账"); // 清空所有输入框的内容 foreach (var batch in batchs) { batch.text = ""; } foreach (var code in materialCodes) { code.text = ""; } foreach (var location in storageLocations) { location.text = ""; } foreach (var factory in factories) { factory.text = ""; } foreach (var reason in movementReasons) { reason.text = ""; } foreach (var quantity in quantities) { quantity.text = ""; } foreach (var VARIABLE in kucundidian) { VARIABLE.text = ""; } foreach (var VARIABLE in picichuanshu) { VARIABLE.text = ""; } // 如果不是只使用第一个(提交前onlyFirst为false时),清空绑定记录,以便下一个动作重新绑定 if (!wasOnlyFirst) { rowToAnswerIndex.Clear(); // 清空所有字段的验证状态和已完成标记 fieldValidationStates.Clear(); completedFields.Clear(); Debug.Log("清空绑定记录和验证状态,准备下一个动作的答案验证"); // 重新从框架获取新的动作答案(下一个动作可能不同) LoadAnswersFromFramework(); } else { // 即使是onlyFirst模式,也清空验证状态以便重新验证 fieldValidationStates.Clear(); completedFields.Clear(); } InitializeDropdowns(); TutorialGuideManager.Instance.TriggerNextGuide(queryButton.name); } } } }