1
0
mirror of https://github.com/wangdage12/Snap.Hutao.git synced 2026-07-31 04:10:25 +08:00

材料统计,右键 材料数字处,反推缺少人和数量

This commit is contained in:
mfkvfhpdx
2026-05-03 20:40:25 +08:00
parent f19dbf78a0
commit dead0166a1
8 changed files with 136 additions and 1 deletions
@@ -10,6 +10,18 @@ namespace Snap.Hutao.Model.Metadata.Item;
internal static class MaterialIds
{
/// <summary>
/// 材料统计右键「未完成条目」不追溯的材料(摩拉、经验书、精锻用魔矿)。
/// </summary>
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; // 冒险家的经验
@@ -2435,6 +2435,15 @@ Space Available: {2}</value>
<data name="ViewPageCultivationMaterialStatistics" xml:space="preserve">
<value>Material Statistics</value>
</data>
<data name="ViewPageCultivationStatisticsUnfinishedConsumers" xml:space="preserve">
<value>Unfinished: {0}</value>
</data>
<data name="ViewPageCultivationStatisticsUnfinishedConsumersEmptyList" xml:space="preserve">
<value>No unchecked entries</value>
</data>
<data name="ViewPageCultivationStatisticsConsumerMenuExcluded" xml:space="preserve">
<value>Mora, EXP books, and Mystic Enhancement Ore have no per-entry breakdown.</value>
</data>
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>Go</value>
</data>
@@ -2486,6 +2486,15 @@
<data name="ViewPageCultivationMaterialStatistics" xml:space="preserve">
<value>材料统计</value>
</data>
<data name="ViewPageCultivationStatisticsUnfinishedConsumers" xml:space="preserve">
<value>未完成:{0}</value>
</data>
<data name="ViewPageCultivationStatisticsUnfinishedConsumersEmptyList" xml:space="preserve">
<value>无未勾选条目</value>
</data>
<data name="ViewPageCultivationStatisticsConsumerMenuExcluded" xml:space="preserve">
<value>摩拉、经验书与魔矿不显示未完成条目。</value>
</data>
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>前往</value>
</data>
@@ -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<CultivateEntry, Guid>(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<CultivateEntry> entries = db.Set<CultivateEntry>().AsNoTracking().Where(e => e.ProjectId == projectId);
return [.. db.Set<CultivateItem>().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))];
}
}
}
@@ -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<uint, StatisticsCultivateItem> resultItems,
Guid projectId,
ICultivationMetadataContext context,
ICultivationRepository cultivationRepository)
{
ImmutableArray<(CultivateEntry Entry, CultivateItem Item)> pairs = cultivationRepository.GetCultivateEntryItemPairsByProjectId(projectId);
Dictionary<uint, List<string>> 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<string>? 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<string>? 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,
};
}
}
@@ -44,4 +44,9 @@ internal interface ICultivationRepository : IRepository<CultivateEntryLevelInfor
ImmutableArray<CultivateEntry> GetCultivateEntryImmutableArrayByProjectIdAndItemId(Guid projectId, uint itemId);
Guid GetCultivateProjectIdByEntryId(Guid entryId);
/// <summary>
/// 联表查询某计划下所有养成物品及其所属条目(用于材料统计未勾选条目等)。
/// </summary>
ImmutableArray<(CultivateEntry Entry, CultivateItem Item)> GetCultivateEntryItemPairsByProjectId(Guid projectId);
}
@@ -227,7 +227,13 @@
</DataTemplate>
<DataTemplate x:Key="StatisticsItemTemplate" x:DataType="shvcu:StatisticsCultivateItem">
<shuxcc:HorizontalCard Background="{Binding IsToday, Converter={ThemeResource BoolToStatisticsBrushSelector}}">
<Border Background="Transparent" IsRightTapEnabled="True">
<Border.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem IsEnabled="False" MaxWidth="360" Text="{Binding StatisticsConsumerMenuText, Mode=OneWay}"/>
</MenuFlyout>
</Border.ContextFlyout>
<shuxcc:HorizontalCard Background="{Binding IsToday, Converter={ThemeResource BoolToStatisticsBrushSelector}}">
<shuxcc:HorizontalCard.Left>
<Grid Grid.Column="0">
<shuxc:ItemIcon
@@ -293,6 +299,7 @@
</Grid>
</shuxcc:HorizontalCard.Right>
</shuxcc:HorizontalCard>
</Border>
</DataTemplate>
<DataTemplate x:Key="InventoryItemTemplate" x:DataType="shvcu:InventoryItemView">
@@ -66,6 +66,11 @@ internal sealed class StatisticsCultivateItem
public bool IsToday { get => Inner.IsItemOfToday(offset, true); }
/// <summary>
/// 材料统计右键菜单中展示的「未完成」养成条目说明(含需求量)。
/// </summary>
public string StatisticsConsumerMenuText { get; set; } = string.Empty;
internal bool ExcludedFromPresentation { get; set; }
public static StatisticsCultivateItem Create(Material inner, TimeSpan offset)