From dead0166a15ffc2459803c05af2596bf8f9489be Mon Sep 17 00:00:00 2001 From: mfkvfhpdx Date: Sun, 3 May 2026 20:40:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=90=E6=96=99=E7=BB=9F=E8=AE=A1=EF=BC=8C?= =?UTF-8?q?=E5=8F=B3=E9=94=AE=20=E6=9D=90=E6=96=99=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=A4=84=EF=BC=8C=E5=8F=8D=E6=8E=A8=E7=BC=BA=E5=B0=91=E4=BA=BA?= =?UTF-8?q?=E5=92=8C=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/Metadata/Item/MaterialIds.cs | 12 ++++ .../Resource/Localization/SH.en.resx | 9 +++ .../Snap.Hutao/Resource/Localization/SH.resx | 9 +++ .../Cultivation/CultivationRepository.cs | 23 +++++++ .../Service/Cultivation/CultivationService.cs | 65 +++++++++++++++++++ .../Cultivation/ICultivationRepository.cs | 5 ++ .../UI/Xaml/View/Page/CultivationPage.xaml | 9 ++- .../Cultivation/StatisticsCultivateItem.cs | 5 ++ 8 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/MaterialIds.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/MaterialIds.cs index 382c10b..4c5b170 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/MaterialIds.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Item/MaterialIds.cs @@ -10,6 +10,18 @@ namespace Snap.Hutao.Model.Metadata.Item; internal static class MaterialIds { + /// + /// 材料统计右键「未完成条目」不追溯的材料(摩拉、经验书、精锻用魔矿)。 + /// + public static bool IsExcludedFromStatisticsConsumerMenu(uint materialId) + { + return materialId is Mora + or WanderersAdvice + or AdventurersExperience + or HeroesWit + or MysticEnhancementOre; + } + public const uint Mora = 202U; // 摩拉 public const uint WanderersAdvice = 104001U; // 流浪者的经验 public const uint AdventurersExperience = 104002U; // 冒险家的经验 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx index 3dcd1bf..b32c8e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -2435,6 +2435,15 @@ Space Available: {2} Material Statistics + + Unfinished: {0} + + + No unchecked entries + + + Mora, EXP books, and Mystic Enhancement Ore have no per-entry breakdown. + Go diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 4a65ae1..769ed50 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2486,6 +2486,15 @@ 材料统计 + + 未完成:{0} + + + 无未勾选条目 + + + 摩拉、经验书与魔矿不显示未完成条目。 + 前往 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationRepository.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationRepository.cs index 1a3082c..c1b01e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationRepository.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationRepository.cs @@ -2,10 +2,14 @@ // Licensed under the MIT license. using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; +using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Service.Abstraction; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Linq; namespace Snap.Hutao.Service.Cultivation; @@ -96,4 +100,23 @@ internal sealed partial class CultivationRepository : ICultivationRepository { return this.Single(query => query.Where(entry => entry.InnerId == entryId).Select(entry => entry.InnerId)); } + + public ImmutableArray<(CultivateEntry Entry, CultivateItem Item)> GetCultivateEntryItemPairsByProjectId(Guid projectId) + { + using (IServiceScope scope = ServiceProvider.CreateScope()) + { + AppDbContext db = scope.GetAppDbContext(); + IQueryable entries = db.Set().AsNoTracking().Where(e => e.ProjectId == projectId); + return [.. db.Set().AsNoTracking() + .Join( + entries, + item => item.EntryId, + entry => entry.InnerId, + (item, entry) => new { Entry = entry, Item = item }) + .OrderBy(t => t.Entry.InnerId) + .ThenBy(t => t.Item.ItemId) + .ToList() + .Select(t => (t.Entry, t.Item))]; + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index 6c71220..cd2badd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -2,18 +2,23 @@ // Licensed under the MIT license. using Snap.Hutao.Core.Database; +using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Metadata; +using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Cultivation.Consumption; using Snap.Hutao.Service.Inventory; using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.ViewModel.Cultivation; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ModelItem = Snap.Hutao.Model.Item; @@ -130,6 +135,7 @@ internal sealed partial class CultivationService : ICultivationService } CultivationStatisticsSurplusMerge.Apply(resultItems, context, mergeOptions); + ApplyStatisticsConsumerMenuText(resultItems, projectId, context, cultivationRepository); return new(resultItems); } @@ -305,4 +311,63 @@ internal sealed partial class CultivationService : ICultivationService } } } + + private static void ApplyStatisticsConsumerMenuText( + Dictionary resultItems, + Guid projectId, + ICultivationMetadataContext context, + ICultivationRepository cultivationRepository) + { + ImmutableArray<(CultivateEntry Entry, CultivateItem Item)> pairs = cultivationRepository.GetCultivateEntryItemPairsByProjectId(projectId); + Dictionary> unfinishedSegmentsByMaterial = []; + + foreach ((CultivateEntry entry, CultivateItem item) in pairs.AsSpan()) + { + if (item.IsFinished) + { + continue; + } + + string name = FormatStatisticsConsumerEntryName(entry, context); + string segment = $"{name}×{item.Count}"; + ref List? list = ref CollectionsMarshal.GetValueRefOrAddDefault(unfinishedSegmentsByMaterial, item.ItemId, out _); + list ??= []; + list.Add(segment); + } + + foreach ((uint materialId, StatisticsCultivateItem stat) in resultItems) + { + if (MaterialIds.IsExcludedFromStatisticsConsumerMenu(materialId)) + { + stat.StatisticsConsumerMenuText = SH.ViewPageCultivationStatisticsConsumerMenuExcluded; + continue; + } + + if (unfinishedSegmentsByMaterial.TryGetValue(materialId, out List? segments)) + { + segments.Sort(StringComparer.Ordinal); + stat.StatisticsConsumerMenuText = string.Format( + CultureInfo.CurrentCulture, + SH.ViewPageCultivationStatisticsUnfinishedConsumers, + string.Join(',', segments)); + } + else + { + stat.StatisticsConsumerMenuText = string.Format( + CultureInfo.CurrentCulture, + SH.ViewPageCultivationStatisticsUnfinishedConsumers, + SH.ViewPageCultivationStatisticsUnfinishedConsumersEmptyList); + } + } + } + + private static string FormatStatisticsConsumerEntryName(CultivateEntry entry, ICultivationMetadataContext context) + { + return entry.Type switch + { + CultivateType.AvatarAndSkill => context.GetAvatarItem(entry.Id).Name, + CultivateType.Weapon => context.GetWeaponItem(entry.Id).Name, + _ => Material.Default.Name, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationRepository.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationRepository.cs index 9f32114..a6b7754 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationRepository.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationRepository.cs @@ -44,4 +44,9 @@ internal interface ICultivationRepository : IRepository GetCultivateEntryImmutableArrayByProjectIdAndItemId(Guid projectId, uint itemId); Guid GetCultivateProjectIdByEntryId(Guid entryId); + + /// + /// 联表查询某计划下所有养成物品及其所属条目(用于材料统计未勾选条目等)。 + /// + ImmutableArray<(CultivateEntry Entry, CultivateItem Item)> GetCultivateEntryItemPairsByProjectId(Guid projectId); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml index 4825003..f57814d 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml @@ -227,7 +227,13 @@ - + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs index ff64dc0..7457426 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs @@ -66,6 +66,11 @@ internal sealed class StatisticsCultivateItem public bool IsToday { get => Inner.IsItemOfToday(offset, true); } + /// + /// 材料统计右键菜单中展示的「未完成」养成条目说明(含需求量)。 + /// + public string StatisticsConsumerMenuText { get; set; } = string.Empty; + internal bool ExcludedFromPresentation { get; set; } public static StatisticsCultivateItem Create(Material inner, TimeSpan offset)