Tz2/Assets/Zion/Scripts/ERP/库存物资查询报表/LedgerTransferPostingManage...

1349 lines
56 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;
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<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)>();
/// <summary>
/// 获取当前是否为教学模式
/// </summary>
private bool IsTeachingMode => _cachedCurrentMode == ProcessMode.;
/// <summary>
/// 获取当前是否为考核模式
/// </summary>
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();
}
/// <summary>
/// 初始化下拉框默认选择
/// </summary>
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或没有选项");
}
}
/// <summary>
/// 初始化输入验证系统
/// </summary>
private void InitializeInputValidation()
{
// 缓存当前模式
CacheCurrentMode();
// 从流程框架获取答案并解析
LoadAnswersFromFramework();
// 为所有输入框添加值变化监听
AddInputFieldListeners();
// 初始化输入框状态
UpdateInputFieldStates();
}
/// <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>();
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 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)}]");
}
/// <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 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, "数量");
});
}
}
/// <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))
{
// 答案组已被使用,只判断不清空
//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;
}
}
/// <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
{
// 输入错误,只判断不清空
// 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, "数量");
}
/// <summary>
/// 更新输入框状态,根据前置条件启用/禁用输入框
/// </summary>
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);
}
/// <summary>
/// 设置输入框数组的交互状态
/// </summary>
/// <param name="inputFields">输入框数组</param>
/// <param name="interactable">是否可交互</param>
/// <param name="isBatchField">是否为批次输入框(需要特殊处理)</param>
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}");
}
}
/// <summary>
/// 检查输入框数组中是否有任何输入框被填写
/// </summary>
/// <param name="inputFields">输入框数组</param>
/// <returns>是否有输入框被填写</returns>
private bool IsAnyInputFieldFilled(TMP_InputField[] inputFields)
{
foreach (var inputField in inputFields)
{
if (inputField != null && !string.IsNullOrEmpty(inputField.text.Trim()))
{
return true;
}
}
return false;
}
/// <summary>
/// 智能检查输入框是否完成根据onlyFirst决定检查范围
/// </summary>
/// <param name="inputFields">输入框数组</param>
/// <returns>输入框是否完成</returns>
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);
}
}
/// <summary>
/// 物料编码输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
private void OnMaterialCodeChanged(string value)
{
// 检查物料编码是否完成根据onlyFirst决定检查范围
isMaterialCodeCompleted = IsInputFieldCompleted(materialCodes);
// 更新输入框状态
UpdateInputFieldStates();
Debug.Log($"物料编码状态更新: {isMaterialCodeCompleted} (onlyFirst: {onlyFirst})");
}
/// <summary>
/// 库存地点输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
private void OnStorageLocationChanged(string value)
{
// 检查库存地点是否完成根据onlyFirst决定检查范围
isStorageLocationCompleted = IsInputFieldCompleted(storageLocations);
// 更新输入框状态
UpdateInputFieldStates();
Debug.Log($"库存地点状态更新: {isStorageLocationCompleted} (onlyFirst: {onlyFirst})");
}
/// <summary>
/// 工厂输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
private void OnFactoryChanged(string value)
{
// 检查工厂是否完成根据onlyFirst决定检查范围
isFactoryCompleted = IsInputFieldCompleted(factories);
// 更新输入框状态
UpdateInputFieldStates();
Debug.Log($"工厂状态更新: {isFactoryCompleted} (onlyFirst: {onlyFirst})");
}
/// <summary>
/// 批次输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
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}");
}
/// <summary>
/// 批次输入框获得焦点事件处理(教学模式专用)
/// </summary>
/// <param name="inputField">获得焦点的输入框</param>
private void OnBatchFieldSelected(TMP_InputField inputField)
{
// 检查前置条件是否满足
if (!(isMaterialCodeCompleted && isStorageLocationCompleted && isFactoryCompleted))
{
// 前置条件不满足,立即取消焦点
inputField.DeactivateInputField();
// 显示提示信息
//ShowInputOrderError("请先完成物料编码、库存地点、工厂的输入,再输入批次号");
Debug.Log("教学模式:批次输入框焦点被阻止,前置条件未满足");
}
}
/// <summary>
/// 移动原因输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
private void OnMovementReasonChanged(string value)
{
// 更新输入框状态
UpdateInputFieldStates();
}
/// <summary>
/// 数量输入变化事件处理
/// </summary>
/// <param name="value">输入值</param>
private void OnQuantityChanged(string value)
{
// 更新输入框状态
UpdateInputFieldStates();
}
// /// <summary>
// /// 显示输入顺序错误提示
// /// </summary>
// /// <param name="message">错误消息</param>
// private void ShowInputOrderError(string message)
// {
// // 使用现有的AutoHideScript显示错误提示
// if (autoHideScript != null)
// {
// autoHideScript.ShowObject(message);
// }
//
// Debug.LogWarning($"输入顺序错误: {message}");
// }
/// <summary>
/// 提交前整体校验:若某行任一字段填写,则该字段必须等于该行绑定的标准答案
/// </summary>
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;
}
/// <summary>
/// 验证输入顺序是否有效
/// </summary>
/// <returns>输入顺序是否有效</returns>
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;
}
/// <summary>
/// 从答案数组构建 parameters验证成功时使用
/// </summary>
/// <param name="parameters">参数列表(已包含两个下拉框的值)</param>
private void BuildParametersFromAnswers(List<string> 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);
}
}
}
/// <summary>
/// 从输入框构建 parameters验证失败或默认使用
/// </summary>
/// <param name="parameters">参数列表(已包含两个下拉框的值)</param>
private void BuildParametersFromInputs(List<string> 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<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);
}
if (MotionEngine.GetModule<ProcessManager>().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);
}
}
}
}