通过TryParse 或者 TryGetValue 可以判断是否有值

本文介绍了如何在C#中利用float.TryParse方法转换字符串为浮点数,并通过Dictionary<TKey, TValue>.TryGetValue进行字典值的查找。这两种技术在处理数据时能有效避免异常,提高代码的健壮性。示例代码中创建了一个Dictionary<string, int>实例,并尝试获取键为'linch'的值。
 Dictionary<string, int> dic = new Dictionary<string, int>();
    float.TryParse("1236", out float result);
    dic.TryGetValue("linch", out int t);

通过TryParse 或者 TryGetValue 可以判断是否有值

using Mds.Customization.CodeRule; using Mds.Master.Entities; using Mds.Master.Operation; using Mds.Master.Repositories; using Mds.Toolkit; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; namespace Mds.Master { public class MasterOperationService( IOperationRepository operationRepo, IRepository<SubOperationEntity, Guid> subOperationRepo ) : MdsCrudAppService< OperationEntity, OperationGetOutputDto, Dictionary<string, object>, Guid, OperationGetListInput, OperationCreateInput, OperationUpdateInput >(operationRepo), IMasterOperationService { public async override Task<OperationGetOutputDto> CreateAsync(OperationCreateInput input) { if (string.IsNullOrWhiteSpace(input.OperationCode)) { string code = await CodeService.GenCodeAsync(CodeRuleConstant.OperationCodeRule); input.OperationCode = code; } await base.EnsureEntityUniqueAsync(GuidGenerator.Create(), input, "OperationName"); return await base.CreateAsync(input); } public async override Task<OperationGetOutputDto> UpdateAsync(Guid id, OperationUpdateInput input) { if (string.IsNullOrWhiteSpace(input.OperationCode)) { string code = await CodeService.GenCodeAsync(CodeRuleConstant.OperationCodeRule); input.OperationCode = code; } await base.EnsureEntityUniqueAsync(id, input, "OperationName"); return await base.UpdateAsync(id, input); } #region 外键关联代码 protected override void MapToEntity(OperationUpdateInput updateInput, OperationEntity entity) { base.MapToEntity(updateInput, entity); SetIdForGuids(entity); } protected override void SetIdForGuids(OperationEntity entity) { base.SetIdForGuids(entity); // 1. 筛选当前主工序下的所有子工序(同一层级,保持原始顺序) var subOperations = entity.SubOperations .Where(sub => sub.OperationId == entity.Id) .ToList().OrderBy(sub => sub.StepSequence); // 2. 处理子工序(不排序,保留原始顺序和StepSequence) foreach (var subOp in subOperations) { // 原有逻辑:生成子工序ID + 绑定外键(保留) if (subOp.Id == Guid.Empty) EntityHelper.TrySetId(subOp, () => GuidGenerator.Create(), true); subOp.OperationId = entity.Id; // 原有校验:子工序父编码不能等于主工序编码(保留): Todo: 这里需要在用户编辑页面时就处理,而且这里是多级的不是单级,递归的项目不应该显示在用户的选择项中。 if (subOp.SubOperationId == entity.Id) { throw new BusinessException(MdsDomainErrorCodes.OperationCheck); } // 移除了StepSequence的重排逻辑,保留原始 } // 资源和收费项外键处理(保持不变) foreach (var item in entity.OperationResource) { if (item.Id == Guid.Empty) EntityHelper.TrySetId(item, () => GuidGenerator.Create(), true); item.OperationId = entity.Id; } foreach (var item in entity.OperationChargeItems) { if (item.Id == Guid.Empty) EntityHelper.TrySetId(item, () => GuidGenerator.Create(), true); item.OperationId = entity.Id; } } public async Task<TreeResponseDto<OperationNodeDto>> GetOperationById(OperationGetListInput input) { // 1. 入参校验 if (input.OperationCode.IsNullOrEmpty()) { return new TreeResponseDto<OperationNodeDto> { TotalCount = 0, Items = new() }; } // 2. 筛选主工序(按Code前缀)并分页 var opQry = await operationRepo.GetQueryableAsync(); var mainOpQuery = opQry .Where(op => op.OperationCode.StartsWith(input.OperationCode)) .OrderBy(op => op.OperationCode); var totalMainOpCount = await AsyncExecuter.CountAsync(mainOpQuery); var pagedMainOpIds = await mainOpQuery .Skip(input.SkipCount) .Take(input.MaxResultCount) .Select(op => op.Id) .ToListAsync(); if (!pagedMainOpIds.Any()) { return new TreeResponseDto<OperationNodeDto> { TotalCount = 0, Items = new() }; } // 3. 调用原有树形逻辑 var nestedTrees = await GetNestedTreeAsync(pagedMainOpIds); // 4. 关键:递归拉平嵌套节点,所有节点作为顶级节点 var flatNodes = new List<OperationNodeDto>(); var queue = new Queue<OperationNodeDto>(nestedTrees); // 初始队列放根节点 while (queue.Count > 0) { var current = queue.Dequeue(); // 克隆当前节点,清空children(避免保留嵌套关系) var flatNode = new OperationNodeDto { Id = current.Id, OperationType = current.OperationType, OperationCode = current.OperationCode, OperationName = current.OperationName, Description = current.Description, StandardDuration = current.StandardDuration, IndirectlyHours = current.IndirectlyHours, FixedHours = current.FixedHours, RequiredQc = current.RequiredQc, IsInActive = current.IsInActive, RecordTypeId = current.RecordTypeId, ParentCode = current.ParentCode, // 保留父编码信息 Level = current.Level, StepSequence = current.StepSequence, ParentId = current.ParentId, // 保留父ID信息 Children = new List<OperationNodeDto>() // 强制子节点为空 }; flatNodes.Add(flatNode); // 将子节点入队,继续拉平(递归处理所有层级) foreach (var child in current.Children) { queue.Enqueue(child); } } // 5. 去重(按ID唯一) var uniqueFlatNodes = flatNodes .GroupBy(node => node.Id) .Select(group => group.First()) .ToList(); // 6. 返回拉平后的结果 return new TreeResponseDto<OperationNodeDto> { TotalCount = uniqueFlatNodes.Count, // 总节点数(主+所有子节点) Items = uniqueFlatNodes // 所有节点均为顶级,无嵌套 }; } #endregion #region 查询Operation Tree数据方法 public Task<List<OperationNodeFlatDto>> GetFlatTreeAsync(IEnumerable<Guid> rootIds) => GetFlatNodesAsync(rootIds); public Task<List<OperationNodeFlatDto>> GetFlatTreeAsync(Guid rootId) => GetFlatNodesAsync(new[] { rootId }).ContinueWith(t => t.Result.ToList()); public Task<OperationNodeDto?> GetNestedTreeAsync(Guid rootId) => GetNestedTreeAsync(new[] { rootId }).ContinueWith(t => t.Result.FirstOrDefault()); #region 修改查询方法(返回前端所需格式) // 1. 修改 GetAllOperationsTreeAsync 方法的返回类型为 TreeResponseDto<OperationNodeDto> public async Task<TreeResponseDto<OperationNodeDto>> GetAllOperationsTreeAsync() { var opQry = await operationRepo.GetQueryableAsync(); var allOperationIds = await opQry .Select(o => o.Id) .ToListAsync(); // 获取原始嵌套树形数据 var nestedTrees = await GetNestedTreeAsync(allOperationIds); // 2. 根节点去重(按 Id 唯一,避免同一节点多次作为根节点) var uniqueRoots = nestedTrees .GroupBy(node => node.Id) // 按节点唯一标识分组 .Select(group => group.First()) // 每组只保留第一个节点(去重) .ToList(); // 3. 关键:只保留一级根节点中有子工序的节点(Children 不为空) var filteredRoots = uniqueRoots .Where(root => root.Children != null && root.Children.Any()) .ToList(); // 4. 包装为前端所需格式 return new TreeResponseDto<OperationNodeDto> { TotalCount = filteredRoots.Count, // 过滤后的一级根节点数量 Items = filteredRoots // 仅保留有子工序的一级根节点,其子节点正常保留(无论是否有子工序) }; } #endregion public async Task<List<OperationNodeDto>> GetNestedTreeAsync(IEnumerable<Guid> rootIds) { var flat = await GetFlatNodesAsync(rootIds); if (!flat.Any()) return new(); var roots = new List<OperationNodeDto>(); var nodeById = new Dictionary<Guid, List<OperationNodeDto>>(); // 改成 List! foreach (var item in flat) { var node = new OperationNodeDto { Id = item.Id, OperationType = item.OperationType, OperationCode = item.OperationCode, OperationName = item.OperationName, Description = item.Description, StandardDuration = item.StandardDuration, IndirectlyHours = item.IndirectlyHours, FixedHours = item.FixedHours, RequiredQc = item.RequiredQc, IsInActive = item.IsInActive, RecordTypeId = item.RecordTypeId, ParentCode = item.ParentCode, Level = item.Level, StepSequence = item.StepSequence, ParentId = item.ParentId, Children = new List<OperationNodeDto>() }; // 同一个 Id 可以有多个实例(每条路径一个) if (!nodeById.ContainsKey(item.Id)) nodeById[item.Id] = new List<OperationNodeDto>(); nodeById[item.Id].Add(node); if (item.ParentId == null) { roots.Add(node); } else { // 找到这个父节点的所有实例,子节点挂到所有父实例下(可选) if (nodeById.TryGetValue(item.ParentId.Value, out var parentInstances)) { foreach (var parent in parentInstances) { // 可选:避免重复添加 if (!parent.Children.Any(c => c.Id == node.Id && c.ParentId == item.ParentId)) { parent.Children.Add(node); } } } } } return roots; } #endregion #region 私有方法 private async Task<List<OperationNodeFlatDto>> GetFlatNodesAsync(IEnumerable<Guid> rootIds) { var rootIdList = rootIds?.Where(id => id != Guid.Empty).Distinct().ToHashSet(); if (rootIdList == null || rootIdList.Count == 0) return new(); var opQry = await operationRepo.GetQueryableAsync(); var subOpQry = await subOperationRepo.GetQueryableAsync(); // 1. 收集所有后代 Id var allIds = new HashSet<Guid>(rootIdList); var toProcess = new Queue<Guid>(rootIdList); while (toProcess.Count > 0) { var batch = toProcess.Take(500).ToList(); toProcess = new Queue<Guid>(toProcess.Skip(500)); var childIds = await subOpQry .Where(s => batch.Contains(s.OperationId)) .Select(s => s.SubOperationId) .ToListAsync(); foreach (var childId in childIds) if (allIds.Add(childId)) toProcess.Enqueue(childId); } // 2. 加载所有节点和所有边 var operations = await opQry .Where(o => allIds.Contains(o.Id)) .ToListAsync(); var idToCodeMap = operations.ToDictionary(op => op.Id, op => op.OperationCode); var links = await subOpQry .Where(s => allIds.Contains(s.OperationId) && allIds.Contains(s.SubOperationId)) .OrderBy(s => s.OperationId) .ThenBy(s => s.StepSequence) .ToListAsync(); // 3. 关键:不再用 Dictionary 去重!直接生成多条记录 var result = new List<OperationNodeFlatDto>(); // 用于计算 Level 和路径 var nodeInfo = new Dictionary<Guid, (int Level, Guid? ParentId, int StepSequence)>(); var queue = new Queue<(Guid Id, int Level, Guid? ParentId, int StepSequence)>(); // 初始化根节点 foreach (var rootId in rootIdList) { if (operations.Any(o => o.Id == rootId)) { queue.Enqueue((rootId, 0, null, 0)); nodeInfo[rootId] = (0, null, 0); } } while (queue.Count > 0) { var (currentId, level, parentId, seq) = queue.Dequeue(); var op = operations.First(o => o.Id == currentId); var parentCode = parentId.HasValue ? idToCodeMap.TryGetValue(parentId.Value, out var code) ? code : null : null; // 每出现一次,就生成一条记录(支持多父!) result.Add(new OperationNodeFlatDto { Id = op.Id, OperationType = op.OperationType, OperationCode = op.OperationCode, OperationName = op.OperationName, Description = op.Description, StandardDuration = op.StandardDuration, IndirectlyHours = op.IndirectlyHours, FixedHours = op.FixedHours, RequiredQc = op.RequiredQc, IsInActive = op.IsInActive, RecordTypeId = op.RecordTypeId, ParentCode = parentCode, ParentId = parentId, Level = level, StepSequence = seq }); // 查找当前节点的所有子边 var children = links.Where(l => l.OperationId == currentId).ToList(); foreach (var child in children) { var childId = child.SubOperationId; var childLevel = level + 1; // 即使 childId 已出现过,也要再入队!(支持多路径) queue.Enqueue((childId, childLevel, currentId, child.StepSequence)); } } return result; } #endregion // 在 MasterOperationService 中添加 /// <summary> /// 计算指定工序ID集合的层级(根节点0,子节点=父节点+1) /// </summary> private async Task<Dictionary<Guid, int>> CalculateOperationLevelsAsync(IEnumerable<Guid> operationIds) { var levelMap = new Dictionary<Guid, int>(); if (!operationIds.Any()) return levelMap; // 1. 获取所有相关的父子关系(父OperationId -> 子SubOperationId) var subOpQry = await subOperationRepo.GetQueryableAsync(); var parentChildRelations = await subOpQry .Where(s => operationIds.Contains(s.OperationId) || operationIds.Contains(s.SubOperationId)) .Select(s => new { ParentId = s.OperationId, ChildId = s.SubOperationId }) .ToListAsync(); // 2. 找出所有根节点(没有父节点的工序) var allChildIds = parentChildRelations.Select(r => r.ChildId).ToHashSet(); var rootIds = operationIds.Where(id => !allChildIds.Contains(id)).ToList(); // 3. 初始化根节点层级为0 foreach (var rootId in rootIds) { levelMap[rootId] = 0; } // 4. 递归计算子节点层级(BFS避免栈溢出) var queue = new Queue<Guid>(rootIds); // 从根节点开始遍历 while (queue.Count > 0) { var parentId = queue.Dequeue(); var parentLevel = levelMap[parentId]; // 找出当前父节点的所有子节点 var childIds = parentChildRelations .Where(r => r.ParentId == parentId && operationIds.Contains(r.ChildId)) .Select(r => r.ChildId) .ToList(); foreach (var childId in childIds) { if (!levelMap.ContainsKey(childId)) // 避免重复计算 { levelMap[childId] = parentLevel + 1; // 子节点层级=父节点+1 queue.Enqueue(childId); // 继续计算子节点的子节点 } } } // 5. 处理孤立节点(无任何父子关系,默认层级0) foreach (var id in operationIds) { if (!levelMap.ContainsKey(id)) { levelMap[id] = 0; } } return levelMap; } public async Task<PagedResultDto<OperationGetOutputDto>> GetListTopLevelAsync(OperationGetListInput input) { var opQry = await operationRepo.GetQueryableAsync(); var subOpQry = await subOperationRepo.GetQueryableAsync(); List<Guid> subOpIds = await subOpQry .Select(s => s.SubOperationId) .Distinct() .ToListAsync(); opQry = opQry.Where(o => !subOpIds.Contains(o.Id)); opQry = FilterHelper.CreateFilterQuery(input, opQry); if (!string.IsNullOrWhiteSpace(input.FilterOpt)) { var filterOpt = JObject.Parse(input.FilterOpt); var keywords = filterOpt.ContainsKey("keywords") ? filterOpt["keywords"]?.ToString() : null; if (!string.IsNullOrWhiteSpace(keywords)) { opQry = opQry.Where(o => o.OperationCode.Contains(keywords) || o.OperationName.Contains(keywords)); } } var entities = await opQry.ToListAsync(); var dtos = ObjectMapper.Map<List<OperationEntity>, List<OperationGetOutputDto>>(entities); return new PagedResultDto<OperationGetOutputDto>(dtos.Count, dtos); } /// <summary> /// 将对象转换为 Dictionary<string, object>(键为属性名,为属性) /// </summary> public static Dictionary<string, object> Convert(OperationNodeFlatDto obj) { if (obj == null) throw new ArgumentNullException(nameof(obj), "对象不能为null"); var dict = new Dictionary<string, object>(); // 获取对象的所有公共实例属性 PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { // 忽略索引器(如this[]) if (prop.GetIndexParameters().Length > 0) continue; // 获取属性(支持null) object value = prop.GetValue(obj); dict[prop.Name] = value; } return dict; } public override async Task<OperationGetOutputDto> GetAsync(Guid id) { var operation = await operationRepo.GetAsync(id); // 计算当前工序的层级 var levelMap = await CalculateOperationLevelsAsync(new[] { id }); var dto = ObjectMapper.Map<OperationEntity, OperationGetOutputDto>(operation); dto.Level = levelMap.TryGetValue(id, out var level) ? level : 0; return dto; } public async Task<PagedResultDto<OperationGetOutputDto>> GetListWithFilterAsync(OperationGetListInput input) { // 1. 解析前端传入的FilterOpt JSON字符串 OperationFilterOptions? filter = null; if (!string.IsNullOrWhiteSpace(input.FilterOpt)) { try { var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore }; filter = JsonConvert.DeserializeObject<OperationFilterOptions>(input.FilterOpt, settings); } catch (Exception) { throw new BusinessException("筛选条件格式错误,请传入有效的JSON字符串!"); } } Guid? excludedId = null; if (filter != null && !string.IsNullOrWhiteSpace(filter.Filter_Id)) { string parentIdStr = filter.Filter_Id.Trim(); const string prefix = "opt_"; // 检查是否以 "opt_" 开头 if (parentIdStr.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // 提取 "opt_" 后面的部分 string suffix = parentIdStr.Substring(prefix.Length).Trim(); // 按空格分割操作符代码和目标 string[] parts = suffix.Split(new[] { ' ' }, 2); // 尝试解析操作符 if (parts.Length > 0 && Enum.TryParse<OperatorType>(parts[0], out var operatorType)) { // 只有当操作符是 NotEqual 时,才执行排除逻辑 if (operatorType == OperatorType.NotEqual && parts.Length > 1) { // 解析真正的ID string idValue = parts[1].Trim(); if (Guid.TryParse(idValue, out Guid parsedId)) { excludedId = parsedId; } } } } } } } } 你可以获取他的扁平化数据来判断
11-26
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; using XCharts.Runtime; public class ImportWaterQualityChart : MonoBehaviour { [SerializeField] public XCharts.Runtime.LineChart lineChart; [SerializeField] private Button customButton; [SerializeField] private TMP_InputField startDateInput; [SerializeField] private TMP_InputField startTimeInput; [SerializeField] private TMP_InputField endDateInput; [SerializeField] private TMP_InputField endTimeInput; [SerializeField] private WebDataUpdater webDataUpdater; [SerializeField] private TextMeshProUGUI Tips; [SerializeField] private Button dayButton; // 天按钮(当天24小时) [SerializeField] private Button weekButton; // 周按钮(本周7天) [SerializeField] private Button monthButton; // 月按钮(本月所有天) private bool isCustomDateOnly = false; private TimeRange currentRange = TimeRange.Day; private DateTime customStartTime; private DateTime customEndTime; private List<ImportHourlyData> importHourlyData = new List<ImportHourlyData>(); private Coroutine hideTipsCoroutine; private bool isCustomWithHour = false; public WebDataUpdater WebDataUpdater; private Dictionary<string, string> fieldNameMap = new Dictionary<string, string> { {"W01001_Rtd", "PH"}, //{"W01010_Rtd", "水温"}, {"W21003_Rtd", "氨氮"}, {"W21011_Rtd", "总磷"}, //{"W00000_Rtd", "污水"}, {"W21001_Rtd", "总氮"}, {"W01018_Rtd", "COD"}, {"W00000l_Rtd", "总量"} }; private void Awake() { if (lineChart == null) lineChart = GetComponent<XCharts.Runtime.LineChart>(); DateTime now = DateTime.Now; // 日期输入框默认 startDateInput.text = now.AddDays(-1).ToString("yyyy-MM-dd"); endDateInput.text = now.ToString("yyyy-MM-dd"); // 小时输入框默认 + 提示文本 startTimeInput.text = "22"; startTimeInput.placeholder.GetComponent<TextMeshProUGUI>().text = "输入0-23(可选)"; endTimeInput.text = "14"; endTimeInput.placeholder.GetComponent<TextMeshProUGUI>().text = "输入0-23(可选)"; if (webDataUpdater == null) webDataUpdater = FindObjectOfType<WebDataUpdater>(); // 绑定天/周/月按钮点击事件 if (dayButton != null) dayButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Day)); if (weekButton != null) weekButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Week)); if (monthButton != null) monthButton.onClick.AddListener(() => OnTimeRangeButtonClicked(TimeRange.Month)); if (customButton != null) customButton.onClick.AddListener(() => OnApplyCustomButtonClicked()); } private void OnEnable() { WebDataUpdater.ImportHourlyUpdated += OnImportHourlyDataUpdated; UpdateTimeRange(currentRange); } private void OnDisable() { WebDataUpdater.ImportHourlyUpdated -= OnImportHourlyDataUpdated; } /// 天/周/月按钮点击回调 private void OnTimeRangeButtonClicked(TimeRange targetRange) { currentRange = targetRange; // 更新当前时间范围 UpdateTimeRange(currentRange); // 触发数据请求和图表刷新 ShowTips($"{GetRangeDisplayName(targetRange)}数据加载中..."); // 提示用户 } private string GetRangeDisplayName(TimeRange range) { return range switch { TimeRange.Day => "当天", TimeRange.Week => "本周", TimeRange.Month => "本月", _ => "自定义" }; } private void OnImportHourlyDataUpdated(List<ImportHourlyData> data, string type, string from) { if (type != "between" || from != "import") { return; } importHourlyData = data ?? new List<ImportHourlyData>(); //Debug.Log($"接收主动请求的小时数据量: {importHourlyData.Count} 条"); RefreshChartWithData(); } public void OnApplyCustomButtonClicked() { if (TryParseCustomTimeRange(out DateTime start, out DateTime end, out bool userEnteredTime)) { //Debug.Log($"解析后的自定义时间范围:start={start:yyyy-MM-dd HH:mm:ss}, end={end:yyyy-MM-dd HH:mm:ss}"); customStartTime = start; customEndTime = end; StartCoroutine(WebDataUpdater.GetData(start, end, "between", "import")); currentRange = TimeRange.Custom; } } /// <summary> /// 解析自定义时间输入,优化小时模式处理 /// </summary> private bool TryParseCustomTimeRange(out DateTime start, out DateTime end, out bool userEnteredTime) { start = DateTime.Now; end = DateTime.Now; userEnteredTime = false; isCustomDateOnly = false; isCustomWithHour = false; // 重置标志位 bool hasValidStartHour = false; bool hasValidEndHour = false; try { string startDate = startDateInput.text.Trim(); string startTime = startTimeInput.text.Trim(); string endDate = endDateInput.text.Trim(); string endTime = endTimeInput.text.Trim(); bool hasStartDate = !string.IsNullOrEmpty(startDate); bool hasStartTime = !string.IsNullOrEmpty(startTime); bool hasEndDate = !string.IsNullOrEmpty(endDate); bool hasEndTime = !string.IsNullOrEmpty(endTime); // 处理开始时间(仅小时):新增有效小时判断 if (hasStartTime) { if (startTime.Contains(":")) { startTime = startTime.Split(':')[0].Trim(); startTimeInput.text = startTime; // 更新输入框显示 } if (int.TryParse(startTime, out int startHour) && startHour >= 0 && startHour <= 23) { startTime = $"{startHour:00}:00"; userEnteredTime = true; hasValidStartHour = true; // 标记为“有效小时” } else { ShowTips("开始小时必须是0-23之间的数字"); return false; } } // 处理结束时间(仅小时):新增有效小时判断 if (hasEndTime) { if (endTime.Contains(":")) { endTime = endTime.Split(':')[0].Trim(); endTimeInput.text = endTime; } if (int.TryParse(endTime, out int endHour) && endHour >= 0 && endHour <= 23) { endTime = $"{endHour:00}:00"; userEnteredTime = true; hasValidEndHour = true; // 标记为“有效小时” } else { ShowTips("结束小时必须是0-23之间的数字"); return false; } } isCustomWithHour = hasValidStartHour || hasValidEndHour; // 关键修复:仅当有至少一个有效小时时,才判定为“按小时显示” isCustomDateOnly = hasStartDate && hasEndDate && !isCustomWithHour; // 仅当“有日期+无任何有效小时”时,才判定为“按天显示” // 补全默认时间(无有效小时时,默认当天00:00/23:00) if (hasStartDate && !hasValidStartHour) startTime = "00:00"; if (hasEndDate && !hasValidEndHour) endTime = "23:00"; string startDateTimeStr = string.IsNullOrEmpty(startTime) ? startDate : $"{startDate} {startTime}"; string endDateTimeStr = string.IsNullOrEmpty(endTime) ? endDate : $"{endDate} {endTime}"; if (DateTime.TryParse(startDateTimeStr, out start) && DateTime.TryParse(endDateTimeStr, out end)) { if (end < start) { ShowTips("结束时间不能早于开始时间"); return false; } return true; } } catch (Exception e) { Debug.LogError($"时间解析异常: {e.Message}\n{e.StackTrace}"); } ShowTips("请选择正确的时间范围,例如格式为2025-9-12 18"); return false; } private void ShowTips(string message) { if (Tips == null) return; if (hideTipsCoroutine != null) StopCoroutine(hideTipsCoroutine); Tips.text = message; Tips.gameObject.SetActive(true); hideTipsCoroutine = StartCoroutine(HideTipsAfterDelay(2f)); } private IEnumerator HideTipsAfterDelay(float delay) { yield return new WaitForSeconds(delay); Tips.gameObject.SetActive(false); hideTipsCoroutine = null; } /// <summary> /// 刷新图表 /// </summary> /// <param name="range"></param> private void UpdateTimeRange(TimeRange range) { DateTime start = DateTime.Now; DateTime end = DateTime.Now; switch (range) { case TimeRange.Day: // 当天:00:00到当前时间 start = DateTime.Today; end = DateTime.Now; break; case TimeRange.Week: // 新增:本周逻辑(周一00:00到当前时间) int daysToMonday = (int)DateTime.Now.DayOfWeek - (int)DayOfWeek.Monday; if (daysToMonday < 0) daysToMonday += 7; // 处理周日(转为上周周一) start = DateTime.Today.AddDays(-daysToMonday); // 本周一00:00 end = DateTime.Now; // 当前时间 break; case TimeRange.Month: // 本月:1号00:00到当前时间(自动适配30/31天) start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); end = DateTime.Now; break; case TimeRange.Custom: start = customStartTime; end = customEndTime; break; } if (webDataUpdater != null) { StartCoroutine(webDataUpdater.GetData(start, end, "between", "import")); } else { Debug.LogError("WebDataUpdater未赋,请检查引用"); } } /// <summary> /// 获取当前时间范围 /// </summary> private void RefreshChartWithData() { var xAxis = lineChart.EnsureChartComponent<XAxis>(); List<string> labels; if (currentRange == TimeRange.Custom) { labels = GenerateCustomTimeLabels(customStartTime, customEndTime); } else { labels = GenerateTimeLabels(currentRange); } xAxis.data.Clear(); xAxis.data.AddRange(labels); xAxis.axisLabel.SetComponentDirty(); foreach (var serie in lineChart.series) { serie.ClearData(); var fieldName = fieldNameMap.FirstOrDefault(kv => kv.Value == serie.serieName).Key; if (!string.IsNullOrEmpty(fieldName)) { FillSerieWithRealData(serie, labels, fieldName); } else { Debug.LogWarning($"未找到与系列名 {serie.serieName} 匹配的字段,请检查配置"); } } lineChart.RefreshChart(); } /// <summary> /// 填充系列数据 /// </summary> /// <param name="serie"></param> /// <param name="labels"></param> /// <param name="dataField"></param> private void FillSerieWithRealData(Serie serie, List<string> labels, string dataField) { serie.ClearData(); if (importHourlyData.Count > 0) { var firstTime = DateTime.TryParse(importHourlyData[0].create_time, out var ft) ? ft : DateTime.MinValue; var lastTime = DateTime.TryParse(importHourlyData.Last().create_time, out var lt) ? lt : DateTime.MinValue; //Debug.Log($"数据时间范围: {firstTime:yyyy-MM-dd HH:mm} 至 {lastTime:yyyy-MM-dd HH:mm}"); } foreach (var label in labels) { if (DateTime.TryParse(label, out DateTime labelTime)) { var dataPoint = importHourlyData.FirstOrDefault(d => { if (!DateTime.TryParse(d.create_time, out DateTime dataTime)) { Debug.LogWarning($"数据时间格式错误: {d.create_time}"); return false; } // 修复:dataTime <= labelTime.AddHours(1) return dataTime >= labelTime && dataTime <= labelTime.AddHours(1); }); if (dataPoint != null && TryGetDataValue(dataPoint, dataField, out double value)) { serie.AddYData((float)value); //Debug.Log($"匹配成功: {labelTime:HH:mm} 对应: {value}"); } else { serie.AddYData(0f); // Debug.LogWarning($"无匹配数据: {labelTime:yyyy-MM-dd HH:mm}"); } } else { serie.AddYData(0f); Debug.LogWarning($"标签时间解析失败: {label}"); } } } /// <summary> /// 填充指定系列数据 /// </summary> /// <param name="data"></param> /// <param name="fieldName"></param> /// <param name="value"></param> /// <returns></returns> private bool TryGetDataValue(ImportHourlyData data, string fieldName, out double value) { value = 0; string fieldValue = fieldName switch { "W01001_Rtd" => data.W01001_Rtd, //"W01010_Rtd" => data.W01010_Rtd, "W21003_Rtd" => data.W21003_Rtd, "W21011_Rtd" => data.W21011_Rtd, //"W00000_Rtd" => data.W00000_Rtd, "W21001_Rtd" => data.W21001_Rtd, "W01018_Rtd" => data.W01018_Rtd, "W00000l_Rtd" => data.W00000l_Rtd, _ => null }; double.TryParse(fieldValue, out value); return true; } /// <summary> /// 尝试从数据点中获取指定字段的 /// </summary> /// <param name="range"></param> /// <returns></returns> private List<string> GenerateTimeLabels(TimeRange range) { List<string> labels = new List<string>(); DateTime now = DateTime.Now; switch (range) { case TimeRange.Day: // 当天标签:00:00~23:00 for (int hour = 0; hour < 24; hour++) { labels.Add($"{hour:00}:00"); } break; case TimeRange.Week: // 本周标签(仅显示日期,格式:MM-dd) int daysToMonday = (int)now.DayOfWeek - (int)DayOfWeek.Monday; if (daysToMonday < 0) daysToMonday += 7; DateTime monday = now.Date.AddDays(-daysToMonday); for (int i = 0; i < 7; i++) { DateTime currentDay = monday.AddDays(i); labels.Add($"{currentDay:MM-dd}"); // 仅显示月-日,例如:09-19 } break; case TimeRange.Month: // 本月标签:1号~当月最后一天(自动适配30/31天) DateTime firstDayOfMonth = new DateTime(now.Year, now.Month, 1); DateTime lastDayOfMonth = new DateTime(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month)); for (DateTime d = firstDayOfMonth; d <= lastDayOfMonth; d = d.AddDays(1)) { labels.Add($"{d:MM-dd}"); // 格式:09-19 } break; } return labels; } /// <summary> /// 获取指定时间范围内的时间标签 /// </summary> /// <param name="start"></param> /// <param name="end"></param> /// <returns></returns> private List<string> GenerateCustomTimeLabels(DateTime start, DateTime end) { List<string> labels = new List<string>(); TimeSpan span = end - start; // 优化1:用户输入了有效小时 → 强制按小时生成标签(忽略跨度) if (isCustomWithHour) { // 计算从开始到结束的所有小时点(包含开始,不超过结束) DateTime currentHour = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0); while (currentHour <= end) { labels.Add($"{currentHour:yyyy-MM-dd HH:00}"); // 小时级格式:月-日 时:00 currentHour = currentHour.AddHours(1); } } // 优化2:用户未输入小时 → 按时间跨度判断(天/月) else { // 跨度≤31天 → 按天生成标签 if (span.TotalDays <= 31) { DateTime currentDay = start.Date; // 取当天0点 while (currentDay <= end.Date) { labels.Add($"{currentDay:MM-dd}"); // 天级格式:月-日 currentDay = currentDay.AddDays(1); } } // 跨度>31天 → 按月生成标签 else { DateTime currentMonth = new DateTime(start.Year, start.Month, 1); while (currentMonth <= end) { labels.Add($"{currentMonth:yyyy-MM}"); // 月级格式:年-月 currentMonth = currentMonth.AddMonths(1); } } } //Debug.Log($"生成标签数量: {labels.Count}"); if (labels.Count > 0) { //Debug.Log($"标签范围: {labels[0]} 至 {labels.Last()}"); } return labels; } /// <summary> /// 公开接口:获取当前图表的时间标签列表(X轴) /// </summary> /// <returns>时间标签(如“00:00”“09-19”“2025-09”)</returns> public List<string> GetCurrentTimeLabels() { var xAxis = lineChart?.EnsureChartComponent<XAxis>(); return xAxis?.data ?? new List<string>(); } /// <summary> /// 公开接口:获取指定水质指标的数列表 /// </summary> /// <param name="indicatorName">指标名(如“PH”“氨氮”,需与fieldNameMap的一致)</param> /// <returns>数列表(无数据时为0)</returns> public List<float> GetIndicatorData(string indicatorName) { List<float> result = new List<float>(); var timeLabels = GetCurrentTimeLabels(); foreach (var timeLabel in timeLabels) { if (DateTime.TryParse(timeLabel, out DateTime labelTime)) { // 从实际数据中找与 labelTime 匹配的数 float value = FindValueByTimestamp(indicatorName, labelTime); result.Add(value); } else { result.Add(0f); } } return result; } /// <summary> /// 根据时间戳获取指定指标的 /// </summary> /// <param name="indicator"></param> /// <param name="targetTime"></param> /// <returns></returns> private float FindValueByTimestamp(string indicator, DateTime targetTime) { // 从 importHourlyData 中找和 targetTime 匹配的数据 var dataPoint = importHourlyData.FirstOrDefault(d => { if (DateTime.TryParse(d.create_time, out DateTime dataTime)) { return dataTime.Hour == targetTime.Hour; } return false; }); return dataPoint != null ? GetValueFromDataPoint(dataPoint, indicator) : 0f; } /// <summary> /// 公开接口:获取所有需要导出的水质指标名 /// </summary> public List<string> GetExportIndicatorNames() { List<string> allIndicators = new List<string>(); foreach (var indicator in fieldNameMap.Values) { allIndicators.Add(indicator); } return allIndicators; } /// <summary> /// 从数据点中提取指定指标的数(根据fieldNameMap映射关系) /// </summary> /// <param name="dataPoint">实际监测数据点(ImportHourlyData实例)</param> /// <param name="indicatorName">指标名(如"PH")</param> /// <returns>指标数(未找到时返回0)</returns> private float GetValueFromDataPoint(ImportHourlyData dataPoint, string indicatorName) { var fieldEntry = fieldNameMap.FirstOrDefault(kv => kv.Value == indicatorName); if (string.IsNullOrEmpty(fieldEntry.Key)) { Debug.LogWarning($"未找到指标 {indicatorName} 对应的字段映射"); return 0f; } string targetField = fieldEntry.Key; switch (targetField) { case "W01001_Rtd": // PH if (float.TryParse(dataPoint.W01001_Rtd, out float phValue)) return phValue; else return 0f; case "W21003_Rtd": // 氨氮 if (float.TryParse(dataPoint.W21003_Rtd, out float ammoniaValue)) return ammoniaValue; else return 0f; case "W21011_Rtd": // 总磷 if (float.TryParse(dataPoint.W21011_Rtd, out float totalPhosphorusValue)) return totalPhosphorusValue; else return 0f; case "W21001_Rtd": // 总氮 if (float.TryParse(dataPoint.W21001_Rtd, out float totalNitrogenValue)) return totalNitrogenValue; else return 0f; case "W01018_Rtd": // COD if (float.TryParse(dataPoint.W01018_Rtd, out float codValue)) return codValue; else return 0f; case "W00000l_Rtd": // 总量 if (float.TryParse(dataPoint.W00000l_Rtd, out float totalValue)) return totalValue; else return 0f; default: Debug.LogWarning($"未处理的字段 {targetField},请检查fieldNameMap配置"); return 0f; } } public enum TimeRange { Day, Month, Week, Custom } }
09-24
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值