Tz2/Assets/Scripts/FileComponent.cs

1241 lines
51 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 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<float> _scores = new List<float>();
// 文件下载配置管理器
private static ExamFileConfigManager _configManager;
// 已下载文件路径跟踪(题目名称 -> 文件路径列表)
private static Dictionary<string, List<string>> _downloadedFiles = new Dictionary<string, List<string>>();
// 配置文件路径
private static readonly string CONFIG_FILE_PATH = "DataConfig/exam_file_config.json";
// 答题卡按钮引用(需要在运行时设置)
private static Button _answerSheetButton;
// 答题卡文件路径(题目名称 -> 答题卡文件路径)
private static Dictionary<string, string> _answerSheetFiles = new Dictionary<string, string>();
// 文件下载完成事件(文件名 -> 文件路径)
public static event System.Action<string, string> OnFileDownloadCompleted;
/// <summary>
/// 设置答题卡按钮引用
/// </summary>
/// <param name="button">答题卡按钮</param>
public static void SetAnswerSheetButton(Button button)
{
_answerSheetButton = button;
Debug.Log("FileComponent: 答题卡按钮引用已设置");
}
/// <summary>
/// 设置过程步骤的分数
/// </summary>
/// <param name="processSteps"></param>
public static void SetProcessSteps(List<ProcessStep> processSteps)
{
_scores.Clear();
foreach (var step in processSteps)
{
foreach (var action in step.Actions)
{
_scores.Add(action.Score);
}
}
}
/// <summary>
/// 初始化文件下载配置管理器
/// </summary>
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<ExamFileConfigManager>(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方法统一使用字符串版本
/// <summary>
/// 获取指定题目的所有文件下载信息(使用题目名字)
/// </summary>
/// <param name="examName">题目名字</param>
/// <returns>文件下载信息列表</returns>
public static async UniTask<List<FileDownloadInfo>> GetExamFileInfos(string examName)
{
await InitializeConfigManager();
var config = _configManager.GetConfigByExamType(examName);
if (config == null)
{
Debug.LogWarning($"未找到题目 '{examName}' 的文件配置");
return new List<FileDownloadInfo>();
}
return config.files;
}
// 移除了所有ExamQuestionTypes版本的方法重载统一使用字符串题目名称
/// <summary>
/// 获取当前题目的文件下载信息包含名称和URL使用全局题目名字
/// </summary>
/// <returns>文件下载信息列表包含名称和URL</returns>
public static async UniTask<List<(string name, string url)>> GetExamFileInfoWithUrls()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().ExamName;
var fileInfos = await GetExamFileInfos(examName);
return fileInfos.Select(f => (f.name, f.url)).ToList();
}
// 移除了ExamQuestionTypes版本的DownloadSingleFile方法统一使用字符串版本
// /// <summary>
// /// 下载单个文件(通过索引)
// /// </summary>
// /// <param name="examType">考试类型</param>
// /// <param name="fileIndex">文件索引从0开始</param>
// 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<string>();
// }
//
// // 重新获取已下载的文件路径
// 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}");
// }
// }
/// <summary>
/// 从本地StreamingAssets/biaodan目录查找并打开文件使用全局题目名字
/// </summary>
/// <param name="fileName">要查找的文件名称</param>
public static async UniTaskVoid DownloadSingleFile(string fileName)
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().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<string>();
}
// 如果文件不在列表中,则添加
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}");
}
}
/// <summary>
/// 上传当前考试的文件(完全基于配置文件,使用全局题目名字)
/// </summary>
public static async UniTask<(bool, string)> UploadExamFiles()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().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<GlobalDataStorage>().ExamInfo;
// 构建上传数据
UploadExamDto uploadExamDto = new UploadExamDto();
float score = MotionEngine.GetModule<ProcessManager>().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<ProcessManager>().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<ProcessManager>().GetUsedTimeInSeconds()).ToString();
uploadExamStepList.score = score.ToString();
uploadExamStepList.stepList = new List<StepListItem>();
// 获取流程管理器
var processManager = MotionEngine.GetModule<ProcessManager>();
// 获取当前流程集合中的所有步骤
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<WebRequestManager>().PostTextAsync(
url: ApiUrls.TransitInventory.AddSubmitDetail,
jsonData: jsonString, MotionEngine.GetModule<GlobalDataStorage>().ExamInfo.Token
);
// 输出原始JSON数据用于调试
Debug.Log("服务器返回数据: " + jsonText);
// 解析返回结果
var response = JsonConvert.DeserializeObject<MessageDto>(jsonText);
Debug.Log(response);
// }
// catch (Exception ex)
// {
// // 处理异常
// Debug.LogError("上传考试数据时发生错误: " + ex.Message);
// }
}
/// <summary>
/// 上传指定题目的文件(完全基于配置文件,使用题目名字,统一使用 _downloadedFiles 字典)
/// </summary>
/// <param name="examName">题目名字</param>
private static async UniTask<(string, bool)> UploadExamFilesAsync(string examName)
{
string token = MotionEngine.GetModule<GlobalDataStorage>().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<WebRequestManager>().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);
}
}
/// <summary>
/// 解析上传结果提取文件URL
/// </summary>
/// <param name="uploadResult">上传返回的JSON结果</param>
/// <returns>文件URL如果解析失败则返回null</returns>
private static string ParseUploadResult(string uploadResult)
{
try
{
if (string.IsNullOrEmpty(uploadResult))
{
Debug.LogWarning("上传结果为空无法解析文件URL");
return null;
}
// 解析JSON结果
var response = JsonConvert.DeserializeObject<UploadResponse>(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;
}
}
/// <summary>
/// 上传响应数据结构
/// </summary>
[System.Serializable]
private class UploadResponse
{
public int code { get; set; }
public string msg { get; set; }
public UploadFileData data { get; set; }
}
/// <summary>
/// 上传数据信息
/// </summary>
[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; }
}
// /// <summary>
// /// 将两个文件压缩成ZIP包
// /// </summary>
// /// <param name="filePath1">第一个文件路径</param>
// /// <param name="filePath2">第二个文件路径</param>
// /// <returns>ZIP文件路径</returns>
// private static async UniTask<string> 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方法
/// <summary>
/// 下载单个文件(使用题目名字)
/// </summary>
/// <param name="examName">题目名字</param>
/// <param name="fileInfo">文件信息</param>
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<WebRequestManager>().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<GlobalDataStorage>().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方法
/// <summary>
/// 从文件路径列表创建ZIP文件使用题目名字
/// </summary>
/// <param name="filePaths">文件路径列表</param>
/// <param name="examName">题目名字</param>
/// <returns>ZIP文件路径</returns>
private static async UniTask<string> CreateZipFileFromPathsAsync(List<string> 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方法
/// <summary>
/// 获取指定题目的已下载文件路径列表(使用题目名字,统一使用 _downloadedFiles 字典)
/// </summary>
/// <param name="examName">题目名字</param>
/// <returns>已下载文件的本地路径列表</returns>
public static List<string> 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<string>();
}
/// <summary>
/// 检测指定文件路径列表中哪些文件处于打开状态
/// </summary>
/// <param name="filePaths">要检测的文件路径列表</param>
/// <returns>处于打开状态的文件路径列表</returns>
private static List<string> CheckOpenDocuments(List<string> filePaths)
{
List<string> openDocuments = new List<string>();
foreach (string filePath in filePaths)
{
if (IsFileOpen(filePath))
{
openDocuments.Add(filePath);
}
}
return openDocuments;
}
/// <summary>
/// 检测当前题目的所有已下载文件是否处于打开状态(外部可调用,无需传值)
/// </summary>
/// <returns>处于打开状态的文件路径列表</returns>
public static List<string> CheckOpenDocuments()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().ExamName;
var downloadedPaths = GetDownloadedFilePaths(examName);
return CheckOpenDocuments(downloadedPaths);
}
/// <summary>
/// 检测单个文件是否处于打开状态
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>如果文件处于打开状态返回true否则返回false</returns>
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;
}
}
/// <summary>
/// 获取动作的总题目数量
/// </summary>
/// <param name="action">动作对象</param>
/// <returns>题目总数</returns>
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;
}
}
/// <summary>
/// 生成答案列表
/// </summary>
/// <param name="stepIndex">步骤索引</param>
/// <param name="actionIndex">动作索引</param>
/// <param name="action">动作对象</param>
/// <returns>答案列表</returns>
private static List<StepListDto> GenerateAnswersList(int stepIndex, int actionIndex, ProcessStepDescription action)
{
var answers = new List<StepListDto>();
var processManager = MotionEngine.GetModule<ProcessManager>();
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($"<color=green>【框架消息】</color>【GenerateAnswersList】从_questionAnswers读取答案answerKey={answerKey}, 学生答案={answerData.studentAnswer}, 正确答案={answerData.correctAnswer}");
answers.Add(new StepListDto
{
question = answerData.question,
studentAnswer = answerData.studentAnswer,
YAnwser = answerData.correctAnswer
});
}
else
{
Debug.Log($"<color=green>【框架消息】</color>【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方法
/// <summary>
/// 获取当前题目的下载状态信息(使用全局题目名字)
/// </summary>
/// <returns>下载状态信息</returns>
public static async UniTask<DownloadStatusInfo> GetDownloadStatus()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().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版本的状态查询和管理方法
/// <summary>
/// 检测是否为答题卡文件
/// </summary>
/// <param name="fileName">文件名</param>
/// <returns>是否为答题卡文件</returns>
private static bool IsAnswerSheetFile(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return false;
string examName = MotionEngine.GetModule<GlobalDataStorage>().ExamName;
// 特殊处理:物料凭证分析基础知识题目的文件都包含答题卡功能
if (examName == "物料凭证分析基础知识")
{
Debug.Log($"FileComponent: 检测到物料凭证分析基础知识题目,文件 '{fileName}' 被视为答题卡文件");
return true;
}
// 检测文件名中是否包含"答题卡"关键词
return fileName.Contains("答题卡");
}
/// <summary>
/// 显示答题卡按钮
/// </summary>
private static void ShowAnswerSheetButton()
{
if (_answerSheetButton != null)
{
_answerSheetButton.gameObject.SetActive(true);
Debug.Log("FileComponent: 答题卡按钮已显示");
}
else
{
Debug.LogWarning("FileComponent: 答题卡按钮引用未设置,无法显示按钮");
}
}
/// <summary>
/// 隐藏答题卡按钮
/// </summary>
public static void HideAnswerSheetButton()
{
if (_answerSheetButton != null)
{
_answerSheetButton.gameObject.SetActive(false);
Debug.Log("FileComponent: 答题卡按钮已隐藏");
}
}
/// <summary>
/// 答题卡按钮点击处理方法
/// </summary>
public static void OnAnswerSheetButtonClick()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().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}");
}
}
/// <summary>
/// 获取当前题目的答题卡文件路径
/// </summary>
/// <returns>答题卡文件路径如果没有则返回null</returns>
public static string GetCurrentAnswerSheetPath()
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().ExamName;
return _answerSheetFiles.ContainsKey(examName) ? _answerSheetFiles[examName] : null;
}
/// <summary>
/// 打开指定文件名的已下载文件
/// </summary>
/// <param name="fileName">文件名</param>
public static void OpenDownloadedFile(string fileName)
{
string examName = MotionEngine.GetModule<GlobalDataStorage>().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}");
}
}
/// <summary>
/// 下载状态信息
/// </summary>
public class DownloadStatusInfo
{
public int TotalFiles { get; set; }
public int DownloadedFiles { get; set; }
public float Progress { get; set; }
public DownloadStatus Status { get; set; }
}
}
}