using Dto; using MotionFramework; using MotionFramework.Scripts.Runtime.Engine.Engine.Network.WebRequest; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using Cysharp.Threading.Tasks; using DefaultNamespace.ProcessMode; using Framework.Utils; using UnityEngine; using Zion.ERP.Inventory; using System.IO.Compression; using UnityEditor; using System.Linq; using Framework.ProcessMode; using UnityEngine.UI; namespace DefaultNamespace { public static class FileComponent { private static string _localFilePath = string.Empty; private static string _answerFilePath = string.Empty; //答题卡 private static List _scores = new List(); // 文件下载配置管理器 private static ExamFileConfigManager _configManager; // 已下载文件路径跟踪(题目名称 -> 文件路径列表) private static Dictionary> _downloadedFiles = new Dictionary>(); // 配置文件路径 private static readonly string CONFIG_FILE_PATH = "DataConfig/exam_file_config.json"; // 答题卡按钮引用(需要在运行时设置) private static Button _answerSheetButton; // 答题卡文件路径(题目名称 -> 答题卡文件路径) private static Dictionary _answerSheetFiles = new Dictionary(); // 文件下载完成事件(文件名 -> 文件路径) public static event System.Action OnFileDownloadCompleted; /// /// 设置答题卡按钮引用 /// /// 答题卡按钮 public static void SetAnswerSheetButton(Button button) { _answerSheetButton = button; Debug.Log("FileComponent: 答题卡按钮引用已设置"); } /// /// 设置过程步骤的分数 /// /// public static void SetProcessSteps(List processSteps) { _scores.Clear(); foreach (var step in processSteps) { foreach (var action in step.Actions) { _scores.Add(action.Score); } } } /// /// 初始化文件下载配置管理器 /// private static async UniTask InitializeConfigManager() { if (_configManager != null) return; try { Debug.Log("开始初始化文件下载配置管理器"); // 读取配置文件 string configPath = Path.Combine(Application.streamingAssetsPath, CONFIG_FILE_PATH); if (!File.Exists(configPath)) { Debug.LogError($"配置文件不存在:{configPath}"); _configManager = new ExamFileConfigManager(); return; } string jsonContent = await File.ReadAllTextAsync(configPath); _configManager = JsonConvert.DeserializeObject(jsonContent); if (_configManager == null) { Debug.LogError("配置文件解析失败"); _configManager = new ExamFileConfigManager(); return; } // 验证配置 if (!_configManager.Validate()) { Debug.LogError("配置文件验证失败"); } Debug.Log($"文件下载配置管理器初始化完成,共加载 {_configManager.examFileConfigs.Count} 个考试类型配置"); } catch (Exception ex) { Debug.LogError($"初始化文件下载配置管理器失败:{ex.Message}"); _configManager = new ExamFileConfigManager(); } } // 移除了ExamQuestionTypes版本的GetExamFileInfos方法,统一使用字符串版本 /// /// 获取指定题目的所有文件下载信息(使用题目名字) /// /// 题目名字 /// 文件下载信息列表 public static async UniTask> GetExamFileInfos(string examName) { await InitializeConfigManager(); var config = _configManager.GetConfigByExamType(examName); if (config == null) { Debug.LogWarning($"未找到题目 '{examName}' 的文件配置"); return new List(); } return config.files; } // 移除了所有ExamQuestionTypes版本的方法重载,统一使用字符串题目名称 /// /// 获取当前题目的文件下载信息(包含名称和URL,使用全局题目名字) /// /// 文件下载信息列表(包含名称和URL) public static async UniTask> GetExamFileInfoWithUrls() { string examName = MotionEngine.GetModule().ExamName; var fileInfos = await GetExamFileInfos(examName); return fileInfos.Select(f => (f.name, f.url)).ToList(); } // 移除了ExamQuestionTypes版本的DownloadSingleFile方法,统一使用字符串版本 // /// // /// 下载单个文件(通过索引) // /// // /// 考试类型 // /// 文件索引(从0开始) // public static async UniTaskVoid DownloadSingleFileByIndex(ExamQuestionTypes examType, int fileIndex) // { // Debug.Log($"FileComponent.DownloadSingleFileByIndex 被调用,考试类型:{examType},文件索引:{fileIndex}"); // // try // { // // 从配置文件获取该考试类型的所有文件信息 // var fileInfos = await GetExamFileInfos(examType); // if (fileInfos.Count == 0) // { // Debug.LogWarning($"考试类型 '{examType}' 在配置文件中没有配置任何文件"); // return; // } // // if (fileIndex < 0 || fileIndex >= fileInfos.Count) // { // Debug.LogError($"文件索引 {fileIndex} 超出范围,配置文件中共有 {fileInfos.Count} 个文件"); // return; // } // // var targetFile = fileInfos[fileIndex]; // Debug.Log($"开始下载索引为 {fileIndex} 的文件:{targetFile.name}"); // // // 下载单个文件 // await DownloadSingleFileAsync(examType, targetFile); // // // 更新已下载文件列表 // if (!_downloadedFiles.ContainsKey(examType)) // { // _downloadedFiles[examType] = new List(); // } // // // 重新获取已下载的文件路径 // var config = _configManager.GetConfigByExamType(examType); // if (config != null) // { // var downloadedPaths = config.GetDownloadedFilePaths(); // _downloadedFiles[examType] = downloadedPaths; // } // // Debug.Log($"索引为 {fileIndex} 的文件 '{targetFile.name}' 下载完成"); // } // catch (Exception ex) // { // Debug.LogError($"下载索引为 {fileIndex} 的文件时发生错误:{ex.Message}"); // } // } /// /// 从本地StreamingAssets/biaodan目录查找并打开文件(使用全局题目名字) /// /// 要查找的文件名称 public static async UniTaskVoid DownloadSingleFile(string fileName) { string examName = MotionEngine.GetModule().ExamName; Debug.Log($"FileComponent.DownloadSingleFile 被调用,全局题目名字:{examName},文件名称:{fileName}"); try { // 从配置文件获取该题目的所有文件信息 var fileInfos = await GetExamFileInfos(examName); if (fileInfos.Count == 0) { Debug.LogWarning($"题目 '{examName}' 在配置文件中没有配置任何文件"); return; } // 查找指定名称的文件配置 var targetFile = fileInfos.FirstOrDefault(f => f.name == fileName); if (targetFile == null) { Debug.LogError($"在题目 '{examName}' 的配置文件中未找到文件:{fileName}"); return; } // 在StreamingAssets/biaodan目录下查找文件 string biaodanDir = Path.Combine(Application.streamingAssetsPath, "biaodan"); if (!Directory.Exists(biaodanDir)) { Debug.LogError($"目录不存在:{biaodanDir}"); return; } // 根据配置中的fileName查找匹配的文件 string targetFileName = targetFile.fileName; string[] files = Directory.GetFiles(biaodanDir, "*", SearchOption.AllDirectories); // 查找完全匹配或包含目标文件名的文件 string foundFilePath = files.FirstOrDefault(f => { string file = Path.GetFileName(f); return file == targetFileName || file.Contains(targetFileName) || targetFileName.Contains(file); }); if (string.IsNullOrEmpty(foundFilePath)) { // 如果精确匹配失败,尝试使用文件名(不含扩展名)匹配 string fileNameWithoutExt = Path.GetFileNameWithoutExtension(targetFileName); foundFilePath = files.FirstOrDefault(f => { string file = Path.GetFileNameWithoutExtension(f); return file == fileNameWithoutExt || file.Contains(fileNameWithoutExt); }); } if (string.IsNullOrEmpty(foundFilePath) || !File.Exists(foundFilePath)) { Debug.LogError($"在目录 '{biaodanDir}' 中未找到文件:{targetFileName}"); return; } Debug.Log($"在本地找到文件:{foundFilePath},准备打开"); // 检测文件是否已打开 if (IsFileOpen(foundFilePath)) { Debug.Log($"文件 '{fileName}' 已打开,无需重复打开"); return; } // 打开文件 bool openResult = FileUtils.OpenFile(foundFilePath); if (!openResult) { Debug.LogError($"打开文件失败:{foundFilePath}"); return; } Debug.Log($"成功打开文件:{foundFilePath}"); // 更新已下载文件列表(用于后续上传等功能) if (!_downloadedFiles.ContainsKey(examName)) { _downloadedFiles[examName] = new List(); } // 如果文件不在列表中,则添加 if (!_downloadedFiles[examName].Contains(foundFilePath)) { _downloadedFiles[examName].Add(foundFilePath); } // 检测是否为答题卡文件 if (IsAnswerSheetFile(fileName)) { Debug.Log($"检测到答题卡文件:{fileName}"); _answerSheetFiles[examName] = foundFilePath; ShowAnswerSheetButton(); } // 触发文件下载完成事件(保持兼容性) OnFileDownloadCompleted?.Invoke(fileName, foundFilePath); Debug.Log($"文件 '{fileName}' 处理完成"); } catch (Exception ex) { Debug.LogError($"处理文件 '{fileName}' 时发生错误:{ex.Message}"); } } /// /// 上传当前考试的文件(完全基于配置文件,使用全局题目名字) /// public static async UniTask<(bool, string)> UploadExamFiles() { string examName = MotionEngine.GetModule().ExamName; Debug.Log($"FileComponent.UploadExamFiles 被调用,全局题目名字:{examName}"); // 先上传文件,获取文件URL var (fileUrl, success) = await UploadExamFilesAsync(examName); if (!success) { Debug.LogError($"文件上传失败:{fileUrl}"); return (false, fileUrl); // 上传失败,直接返回 } // 然后提交题目数据,将文件URL作为备注 await UploadData(fileUrl); Debug.Log($"题目 '{examName}' 的所有数据和文件上传完成(基于配置文件),文件URL:{fileUrl}"); // } // catch (Exception ex) // { // Debug.LogError($"上传题目 '{examName}' 的文件时发生错误:{ex.Message}"); // } return (true, "提交成功"); } private static async UniTask UploadData(string fileUrl = null) { // try // { ExamInfo examInfo = MotionEngine.GetModule().ExamInfo; // 构建上传数据 UploadExamDto uploadExamDto = new UploadExamDto(); float score = MotionEngine.GetModule().CalculateTotalScore(); // 设置DTO中的所有字段 uploadExamDto.id = "0"; uploadExamDto.appId = examInfo.AppID; uploadExamDto.courseId = examInfo.CourseId; uploadExamDto.examId = examInfo.PaperId; uploadExamDto.examinationId = examInfo.ExamRoomId; uploadExamDto.stuId = examInfo.StudentId; uploadExamDto.groupId = examInfo.GroupId; uploadExamDto.machineId = string.Empty; uploadExamDto.batchId = examInfo.BatchId; uploadExamDto.operationType = "3"; uploadExamDto.isComp = uploadExamDto.examId.ToString(); uploadExamDto.currentProcess = "0"; uploadExamDto.sumProcess = "0"; uploadExamDto.score = score.ToString(); uploadExamDto.preScore = "0"; uploadExamDto.inTimes = (MotionEngine.GetModule().GetUsedTimeInSeconds()).ToString(); uploadExamDto.remark = fileUrl; // 将文件URL作为备注 uploadExamDto.stepName = string.Empty; uploadExamDto.testPoint = string.Empty; uploadExamDto.defaultScore = "0"; // 构建考试步骤列表 UploadExamStepList uploadExamStepList = new UploadExamStepList(); uploadExamStepList.time = (MotionEngine.GetModule().GetUsedTimeInSeconds()).ToString(); uploadExamStepList.score = score.ToString(); uploadExamStepList.stepList = new List(); // 获取流程管理器 var processManager = MotionEngine.GetModule(); // 获取当前流程集合中的所有步骤 var steps = processManager.CurrentProcessCollection.Steps; // 遍历所有步骤和动作,创建步骤列表项 for (int stepIndex = 0; stepIndex < steps.Count; stepIndex++) { var step = steps[stepIndex]; float stepScore = (float)step.Score; // 确保转换为float类型 int actionCount = step.Actions.Count; // 精确计算每个动作的分数,完全保留小数点精度 float actionScorePerAction = 0f; if (actionCount > 0) { actionScorePerAction = stepScore / (float)actionCount; // 移除Math.Round,保留原始精度 } for (int actionIndex = 0; actionIndex < step.Actions.Count; actionIndex++) { var action = step.Actions[actionIndex]; // 精确计算每个答案的分数,完全保留小数点精度 int answerCount = GetTotalQuestionsCount(action); float answerScorePerAnswer = 0f; if (answerCount > 0) { answerScorePerAnswer = actionScorePerAction / (float)answerCount; // 移除Math.Round,保留原始精度 } // 计算动作实际得分(考虑错误扣分) float actionActualScore = 0; if (action.IsCompleted) { var currentActionErrors = processManager._incorrectClicksPerStep .Where(kvp => kvp.Key.stepIndex == stepIndex && kvp.Key.actionIndex == actionIndex) .SelectMany(kvp => kvp.Value) .ToList(); if (currentActionErrors.Count > 0) { // 每个错误答案扣一个答案的分数,完全保留小数点精度 float totalDeduction = answerScorePerAnswer * (float)currentActionErrors.Count; // 移除Math.Round,保留原始精度 totalDeduction = Math.Min(totalDeduction, actionScorePerAction); // 扣分不超过动作总分 actionActualScore = Math.Max(0, actionScorePerAction - totalDeduction); } else { actionActualScore = actionScorePerAction; } } // 创建步骤项 StepListItem stepItem = new StepListItem { stepName = step.StepDescription, testPoint = action.Title, setScore = actionActualScore, // 动作实际得分(考虑错误扣分),完全保留小数点 defaultScore = actionScorePerAction, // 动作应该得到的分数(步骤分数 ÷ 动作数量),完全保留小数点 isKey = "1", batch = (stepIndex + 1).ToString(), isTrue = action.IsCompleted ? "1" : "0", cause = "", answers = GenerateAnswersList(stepIndex, actionIndex, action) }; // 添加步骤项到列表 uploadExamStepList.stepList.Add(stepItem); } } // // 创建步骤项 // StepListItem stepItem = new StepListItem // { // stepName = "测试步骤", // testPoint = "测试点", // setScore = 100, // defaultScore = 100, // isKey = "1", // batch = "1", // isTrue = "1", // cause = "" // }; // 序列化考试步骤数据 uploadExamDto.recordContent = JsonConvert.SerializeObject(uploadExamStepList); // 输出上传数据用于调试 string jsonString = JsonConvert.SerializeObject(uploadExamDto); Debug.Log("准备上传的数据: " + jsonString); // 发送网络请求 string jsonText = await MotionEngine.GetModule().PostTextAsync( url: ApiUrls.TransitInventory.AddSubmitDetail, jsonData: jsonString, MotionEngine.GetModule().ExamInfo.Token ); // 输出原始JSON数据用于调试 Debug.Log("服务器返回数据: " + jsonText); // 解析返回结果 var response = JsonConvert.DeserializeObject(jsonText); Debug.Log(response); // } // catch (Exception ex) // { // // 处理异常 // Debug.LogError("上传考试数据时发生错误: " + ex.Message); // } } /// /// 上传指定题目的文件(完全基于配置文件,使用题目名字,统一使用 _downloadedFiles 字典) /// /// 题目名字 private static async UniTask<(string, bool)> UploadExamFilesAsync(string examName) { string token = MotionEngine.GetModule().ExamInfo.Token; try { Debug.Log($"开始上传题目 '{examName}' 的文件(基于配置文件,统一使用 _downloadedFiles 字典)"); // 从配置文件读取该题目的所有文件信息 var fileInfos = await GetExamFileInfos(examName); if (fileInfos.Count == 0) { Debug.LogWarning($"题目 '{examName}' 在配置文件中没有配置任何文件,跳过上传"); return (null, true); // 返回 (null, true) 表示没有文件可上传,但操作成功 } // 获取已下载的文件路径(统一使用 _downloadedFiles 字典) var downloadedPaths = GetDownloadedFilePaths(examName); if (downloadedPaths.Count == 0) { Debug.LogWarning($"题目 '{examName}' 没有已下载的文件,跳过上传(统一使用 _downloadedFiles 字典)"); return (null, true); // 返回 (null, true) 表示没有文件可上传,但操作成功 } // 检测文档是否处于打开状态 var openDocuments = CheckOpenDocuments(downloadedPaths); if (openDocuments.Count > 0) { string openFileNames = string.Join("、", openDocuments.Select(Path.GetFileName)); string errorMessage = $"以下文档仍处于打开状态,请先关闭后再上传:{openFileNames}"; Debug.LogError(errorMessage); // 返回错误信息和失败状态 return ("当前有未关闭的文档,请先关闭在提交!", false); } Debug.Log($"从配置文件读取到 {fileInfos.Count} 个文件配置,找到 {downloadedPaths.Count} 个已下载的文件,准备上传(统一使用 _downloadedFiles 字典)"); // 统一使用压缩包上传,无论文件数量多少 string zipPath = await CreateZipFileFromPathsAsync(downloadedPaths, examName); if (!string.IsNullOrEmpty(zipPath)) { string result = await MotionEngine.GetModule().UploadFileWithFormData( ApiUrls.TransitInventory.UploadFileAndParam, zipPath, token); Debug.Log($"压缩包上传结果:{result}(包含 {downloadedPaths.Count} 个文件,统一使用 _downloadedFiles 字典)"); // 解析上传结果,提取文件URL string fileUrl = ParseUploadResult(result); Debug.Log($"解析出的文件URL:{fileUrl}"); // 上传完成后删除临时ZIP文件 if (File.Exists(zipPath)) { File.Delete(zipPath); Debug.Log($"已删除临时ZIP文件:{zipPath}"); } return (fileUrl, true); // 返回解析出的文件URL和成功状态 } else { Debug.LogError("创建ZIP文件失败"); return ("创建ZIP文件失败", false); } } catch (Exception ex) { Debug.LogError($"上传题目 '{examName}' 的文件时发生错误:{ex.Message}"); return (ex.Message, false); } } /// /// 解析上传结果,提取文件URL /// /// 上传返回的JSON结果 /// 文件URL,如果解析失败则返回null private static string ParseUploadResult(string uploadResult) { try { if (string.IsNullOrEmpty(uploadResult)) { Debug.LogWarning("上传结果为空,无法解析文件URL"); return null; } // 解析JSON结果 var response = JsonConvert.DeserializeObject(uploadResult); if (response?.code == 200 && response?.data?.url != null) { Debug.Log($"成功解析文件URL:{response.data.url}"); return response.data.url; } else { Debug.LogWarning($"上传失败或返回结果异常:code={response?.code}, msg={response?.msg}"); return null; } } catch (Exception ex) { Debug.LogError($"解析上传结果时发生错误:{ex.Message}"); return null; } } /// /// 上传响应数据结构 /// [System.Serializable] private class UploadResponse { public int code { get; set; } public string msg { get; set; } public UploadFileData data { get; set; } } /// /// 上传数据信息 /// [System.Serializable] private class UploadFileData { public string name { get; set; } public string url { get; set; } public string id { get; set; } public string data { get; set; } } // /// // /// 将两个文件压缩成ZIP包 // /// // /// 第一个文件路径 // /// 第二个文件路径 // /// ZIP文件路径 // private static async UniTask CreateZipFileAsync(string filePath1, string filePath2) // { // try // { // // 创建临时ZIP文件路径 // string zipPath = Path.Combine(Application.temporaryCachePath, $"ExamFiles_{DateTime.Now:yyyyMMdd_HHmmss}.zip"); // // // 确保目录存在 // Directory.CreateDirectory(Path.GetDirectoryName(zipPath)); // // // 创建ZIP文件 // if (File.Exists(zipPath)) // { // File.Delete(zipPath); // } // // using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) // { // // 添加第一个文件 // if (File.Exists(filePath1)) // { // string fileName1 = Path.GetFileName(filePath1); // archive.CreateEntryFromFile(filePath1, fileName1); // } // // // 添加第二个文件 // if (File.Exists(filePath2)) // { // string fileName2 = Path.GetFileName(filePath2); // archive.CreateEntryFromFile(filePath2, fileName2); // } // } // // Debug.Log($"ZIP文件已创建: {zipPath}"); // return zipPath; // } // catch (Exception ex) // { // Debug.LogError($"创建ZIP文件时发生错误: {ex.Message}"); // return string.Empty; // } // } // 移除了ExamQuestionTypes版本的DownloadSingleFileAsync方法 /// /// 下载单个文件(使用题目名字) /// /// 题目名字 /// 文件信息 private static async UniTask DownloadSingleFileAsync(string examName, FileDownloadInfo fileInfo) { try { Debug.Log($"开始下载文件:{fileInfo.name},URL:{fileInfo.url}(来自配置文件)"); fileInfo.downloadStatus = DownloadStatus.Downloading; // 下载文件 var fileData = await MotionEngine.GetModule().GetFileAsync(fileInfo.url); if (fileData == null || fileData.Length == 0) { fileInfo.downloadStatus = DownloadStatus.Failed; fileInfo.errorMessage = "下载的文件数据为空"; Debug.LogError($"下载文件 '{fileInfo.name}' 失败:文件数据为空"); return; } // 创建保存目录 string saveDir = Path.Combine(Application.streamingAssetsPath, "Excel", examName); Directory.CreateDirectory(saveDir); // 生成文件路径 - 修复时间戳位置,确保在文件名和扩展名之间 string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.fileName); string fileExtension = Path.GetExtension(fileInfo.fileName); string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string fileName = $"{fileNameWithoutExt}_{timestamp}_{MotionEngine.GetModule().ExamInfo.StudentId}{fileExtension}"; fileInfo.localFilePath = Path.Combine(saveDir, fileName); Debug.Log($"生成文件名 - 原始文件名:{fileInfo.fileName},处理后:{fileName}"); // 保存文件 await File.WriteAllBytesAsync(fileInfo.localFilePath, fileData); fileInfo.isDownloaded = true; fileInfo.downloadStatus = DownloadStatus.Completed; fileInfo.errorMessage = null; Debug.Log($"文件 '{fileInfo.name}' 下载完成,保存路径:{fileInfo.localFilePath}(基于配置文件)"); // 触发文件下载完成事件 OnFileDownloadCompleted?.Invoke(fileInfo.name, fileInfo.localFilePath); // 检测是否为答题卡文件 if (IsAnswerSheetFile(fileInfo.name)) { Debug.Log($"检测到答题卡文件:{fileInfo.name}"); _answerSheetFiles[examName] = fileInfo.localFilePath; ShowAnswerSheetButton(); } // 打开文件 FileUtils.OpenFile(fileInfo.localFilePath); } catch (Exception ex) { fileInfo.downloadStatus = DownloadStatus.Failed; fileInfo.errorMessage = ex.Message; Debug.LogError($"下载文件 '{fileInfo.name}' 时发生错误:{ex.Message}"); } } // 移除了ExamQuestionTypes版本的CreateZipFileFromPathsAsync方法 /// /// 从文件路径列表创建ZIP文件(使用题目名字) /// /// 文件路径列表 /// 题目名字 /// ZIP文件路径 private static async UniTask CreateZipFileFromPathsAsync(List filePaths, string examName) { try { Debug.Log($"开始创建ZIP包,包含 {filePaths.Count} 个文件(基于配置文件)"); // 创建临时ZIP文件路径 string zipPath = Path.Combine(Application.temporaryCachePath, $"{examName}_Files_{DateTime.Now:yyyyMMdd_HHmmss}.zip"); // 确保目录存在 Directory.CreateDirectory(Path.GetDirectoryName(zipPath)); // 删除已存在的ZIP文件 if (File.Exists(zipPath)) { File.Delete(zipPath); } using (ZipArchive archive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) { int addedCount = 0; // 遍历配置文件中的所有文件路径 foreach (string filePath in filePaths) { if (File.Exists(filePath)) { string fileName = Path.GetFileName(filePath); archive.CreateEntryFromFile(filePath, fileName); addedCount++; Debug.Log($"已添加文件到ZIP:{fileName}(来自配置文件)"); } else { Debug.LogWarning($"文件不存在,跳过:{filePath}(配置文件中的文件)"); } } Debug.Log($"ZIP包创建完成,共添加 {addedCount} 个文件(基于配置文件中的 {filePaths.Count} 个文件)"); } Debug.Log($"ZIP文件已创建: {zipPath}(基于配置文件)"); return zipPath; } catch (Exception ex) { Debug.LogError($"创建ZIP文件时发生错误: {ex.Message}"); return string.Empty; } } // 移除了ExamQuestionTypes版本的GetDownloadedFilePaths方法 /// /// 获取指定题目的已下载文件路径列表(使用题目名字,统一使用 _downloadedFiles 字典) /// /// 题目名字 /// 已下载文件的本地路径列表 public static List GetDownloadedFilePaths(string examName) { Debug.Log($"GetDownloadedFilePaths 被调用,题目名字:{examName}"); if (_downloadedFiles.ContainsKey(examName)) { // 过滤掉不存在的文件 var existingFiles = _downloadedFiles[examName].Where(path => File.Exists(path)).ToList(); _downloadedFiles[examName] = existingFiles; Debug.Log($"获取题目 '{examName}' 的已下载文件路径,共 {existingFiles.Count} 个文件"); return existingFiles; } Debug.Log($"题目 '{examName}' 没有已下载的文件记录"); return new List(); } /// /// 检测指定文件路径列表中哪些文件处于打开状态 /// /// 要检测的文件路径列表 /// 处于打开状态的文件路径列表 private static List CheckOpenDocuments(List filePaths) { List openDocuments = new List(); foreach (string filePath in filePaths) { if (IsFileOpen(filePath)) { openDocuments.Add(filePath); } } return openDocuments; } /// /// 检测当前题目的所有已下载文件是否处于打开状态(外部可调用,无需传值) /// /// 处于打开状态的文件路径列表 public static List CheckOpenDocuments() { string examName = MotionEngine.GetModule().ExamName; var downloadedPaths = GetDownloadedFilePaths(examName); return CheckOpenDocuments(downloadedPaths); } /// /// 检测单个文件是否处于打开状态 /// /// 文件路径 /// 如果文件处于打开状态返回true,否则返回false private static bool IsFileOpen(string filePath) { try { // 尝试以独占模式打开文件 using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { // 如果能成功打开,说明文件没有被其他进程占用 return false; } } catch (IOException) { // 如果抛出IOException,说明文件被其他进程占用(可能是打开状态) return true; } catch (UnauthorizedAccessException) { // 如果抛出UnauthorizedAccessException,也可能是文件被占用 return true; } catch (Exception ex) { // 其他异常,记录日志但不认为文件是打开的 Debug.LogWarning($"检测文件 '{filePath}' 状态时发生异常:{ex.Message}"); return false; } } /// /// 获取动作的总题目数量 /// /// 动作对象 /// 题目总数 private static int GetTotalQuestionsCount(ProcessStepDescription action) { switch (action.ActionType) { case ProcessActionType.默认: return action.TargetObjects.Count; case ProcessActionType.判断题: return action.JudgmentQuestions?.Count ?? 0; case ProcessActionType.多选题: return action.MultipleChoiceQuestions?.Count ?? 0; default: return 0; } } /// /// 生成答案列表 /// /// 步骤索引 /// 动作索引 /// 动作对象 /// 答案列表 private static List GenerateAnswersList(int stepIndex, int actionIndex, ProcessStepDescription action) { var answers = new List(); var processManager = MotionEngine.GetModule(); switch (action.ActionType) { case ProcessActionType.判断题: if (action.JudgmentQuestions != null) { for (int i = 0; i < action.JudgmentQuestions.Count; i++) { var question = action.JudgmentQuestions[i]; var answerKey = (stepIndex, actionIndex, i); if (processManager._questionAnswers.ContainsKey(answerKey)) { var answerData = processManager._questionAnswers[answerKey]; Debug.Log($"【框架消息】【GenerateAnswersList】从_questionAnswers读取答案:answerKey={answerKey}, 学生答案={answerData.studentAnswer}, 正确答案={answerData.correctAnswer}"); answers.Add(new StepListDto { question = answerData.question, studentAnswer = answerData.studentAnswer, YAnwser = answerData.correctAnswer }); } else { Debug.Log($"【框架消息】【GenerateAnswersList】answerKey={answerKey} 不存在于_questionAnswers中,使用默认值"); // 如果没有记录,使用默认值 answers.Add(new StepListDto { question = question.Question, studentAnswer = "", YAnwser = question.CorrectAnswer }); } } } break; case ProcessActionType.多选题: if (action.MultipleChoiceQuestions != null) { for (int i = 0; i < action.MultipleChoiceQuestions.Count; i++) { var question = action.MultipleChoiceQuestions[i]; var answerKey = (stepIndex, actionIndex, i); if (processManager._questionAnswers.ContainsKey(answerKey)) { var answerData = processManager._questionAnswers[answerKey]; answers.Add(new StepListDto { question = answerData.question, studentAnswer = answerData.studentAnswer, YAnwser = answerData.correctAnswer }); } else { // 如果没有记录,使用默认值 answers.Add(new StepListDto { question = question.Question, studentAnswer = "", YAnwser = string.Join(",", question.CorrectAnswers) }); } } } break; case ProcessActionType.默认: if (action.TargetObjects != null) { for (int i = 0; i < action.TargetObjects.Count; i++) { var obj = action.TargetObjects[i]; var answerKey = (stepIndex, actionIndex, i); if (processManager._questionAnswers.ContainsKey(answerKey)) { var answerData = processManager._questionAnswers[answerKey]; answers.Add(new StepListDto { question = answerData.question, studentAnswer = answerData.studentAnswer, YAnwser = answerData.correctAnswer }); } else { // 如果没有记录,使用默认值 bool isClicked = action.ClickedObjects.Contains(obj.ObjectName); answers.Add(new StepListDto { question = obj.ObjectName, studentAnswer = isClicked ? "true" : "false", YAnwser = "true" }); } } } break; } return answers; } // 移除了GetExamTypeFromName方法,不再需要枚举映射 // 移除了ExamQuestionTypes版本的GetDownloadStatus方法 /// /// 获取当前题目的下载状态信息(使用全局题目名字) /// /// 下载状态信息 public static async UniTask GetDownloadStatus() { string examName = MotionEngine.GetModule().ExamName; await InitializeConfigManager(); var config = _configManager.GetConfigByExamType(examName); if (config == null) { return new DownloadStatusInfo { TotalFiles = 0, DownloadedFiles = 0, Progress = 1f, Status = DownloadStatus.Completed }; } var downloadedCount = config.GetDownloadedFiles().Count; var totalCount = config.files.Count; var progress = totalCount > 0 ? (float)downloadedCount / totalCount : 1f; var status = progress == 1f ? DownloadStatus.Completed : progress > 0f ? DownloadStatus.Downloading : DownloadStatus.NotStarted; return new DownloadStatusInfo { TotalFiles = totalCount, DownloadedFiles = downloadedCount, Progress = progress, Status = status }; } // 移除了所有ExamQuestionTypes版本的状态查询和管理方法 /// /// 检测是否为答题卡文件 /// /// 文件名 /// 是否为答题卡文件 private static bool IsAnswerSheetFile(string fileName) { if (string.IsNullOrEmpty(fileName)) return false; string examName = MotionEngine.GetModule().ExamName; // 特殊处理:物料凭证分析基础知识题目的文件都包含答题卡功能 if (examName == "物料凭证分析基础知识") { Debug.Log($"FileComponent: 检测到物料凭证分析基础知识题目,文件 '{fileName}' 被视为答题卡文件"); return true; } // 检测文件名中是否包含"答题卡"关键词 return fileName.Contains("答题卡"); } /// /// 显示答题卡按钮 /// private static void ShowAnswerSheetButton() { if (_answerSheetButton != null) { _answerSheetButton.gameObject.SetActive(true); Debug.Log("FileComponent: 答题卡按钮已显示"); } else { Debug.LogWarning("FileComponent: 答题卡按钮引用未设置,无法显示按钮"); } } /// /// 隐藏答题卡按钮 /// public static void HideAnswerSheetButton() { if (_answerSheetButton != null) { _answerSheetButton.gameObject.SetActive(false); Debug.Log("FileComponent: 答题卡按钮已隐藏"); } } /// /// 答题卡按钮点击处理方法 /// public static void OnAnswerSheetButtonClick() { string examName = MotionEngine.GetModule().ExamName; if (!_answerSheetFiles.ContainsKey(examName)) { Debug.LogWarning($"FileComponent: 题目 '{examName}' 没有答题卡文件"); return; } string answerSheetPath = _answerSheetFiles[examName]; if (string.IsNullOrEmpty(answerSheetPath) || !File.Exists(answerSheetPath)) { Debug.LogError($"FileComponent: 答题卡文件不存在:{answerSheetPath}"); return; } // 检测文件是否已打开 if (IsFileOpen(answerSheetPath)) { Debug.Log("FileComponent: 答题卡文件已打开,无需重复打开"); return; } // 打开答题卡文件 bool openResult = FileUtils.OpenFile(answerSheetPath); if (openResult) { Debug.Log($"FileComponent: 成功打开答题卡文件:{answerSheetPath}"); } else { Debug.LogError($"FileComponent: 打开答题卡文件失败:{answerSheetPath}"); } } /// /// 获取当前题目的答题卡文件路径 /// /// 答题卡文件路径,如果没有则返回null public static string GetCurrentAnswerSheetPath() { string examName = MotionEngine.GetModule().ExamName; return _answerSheetFiles.ContainsKey(examName) ? _answerSheetFiles[examName] : null; } /// /// 打开指定文件名的已下载文件 /// /// 文件名 public static void OpenDownloadedFile(string fileName) { string examName = MotionEngine.GetModule().ExamName; var downloadedPaths = GetDownloadedFilePaths(examName); // 查找包含指定文件名的文件路径 var targetPath = downloadedPaths.FirstOrDefault(path => Path.GetFileNameWithoutExtension(path).Contains(fileName) || Path.GetFileName(path).Contains(fileName)); if (!string.IsNullOrEmpty(targetPath) && File.Exists(targetPath)) { // 检测文件是否已打开 if (IsFileOpen(targetPath)) { Debug.Log($"文件 '{fileName}' 已打开,无需重复打开"); return; } // 打开文件 bool openResult = FileUtils.OpenFile(targetPath); if (openResult) { Debug.Log($"成功打开文件:{targetPath}"); } else { Debug.LogError($"打开文件失败:{targetPath}"); } } else { Debug.LogWarning($"未找到已下载的文件:{fileName}"); } } /// /// 下载状态信息 /// public class DownloadStatusInfo { public int TotalFiles { get; set; } public int DownloadedFiles { get; set; } public float Progress { get; set; } public DownloadStatus Status { get; set; } } } }