From ed4626a74ce738ada823f7d15f1b38eca7c8d6fa Mon Sep 17 00:00:00 2001 From: mfkvfhpdx Date: Mon, 4 May 2026 11:16:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=20=E6=88=91=E7=9A=84=E8=A7=92?= =?UTF-8?q?=E8=89=B2=20=E5=90=8C=E6=AD=A5=E8=A7=92=E8=89=B2=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=B7=BB=E5=8A=A0=E6=88=96=E6=9B=B4=E6=96=B0=E5=88=B0?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E5=85=BB=E6=88=90=E8=AE=A1=E5=88=92=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=20=E6=B8=85=E7=A9=BA=E5=B7=B2?= =?UTF-8?q?=E7=BB=8F=E8=A7=92=E8=89=B2=E5=92=8C=E6=AD=A6=E5=99=A8=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=20=E5=8B=BE=E9=80=89=E6=A1=86=E3=80=82=E5=A6=82?= =?UTF-8?q?=E5=8B=BE=E9=80=89=E8=AF=A5=E9=A1=B9=E7=9B=AE=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AE=A1=E5=88=92=E5=89=8D=E4=BC=9A=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=20=E5=9F=B9=E5=85=BB=E8=AE=A1=E5=88=92=E4=B8=AD?= =?UTF-8?q?=EF=BC=8C=E8=A7=92=E8=89=B2=E5=92=8C=E6=AD=A6=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E3=80=82=E5=8F=A6=E5=A4=96=EF=BC=8C=E5=BD=93?= =?UTF-8?q?=E7=82=B9=20=E7=A1=AE=E8=AE=A4=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=AE=8C=E6=95=B0=E6=8D=AE=E5=90=8E=EF=BC=8C=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E7=8E=B0=20=E5=85=BB=E6=88=90=E8=AE=A1=E5=88=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resource/Localization/SH.en.resx | 3 ++ .../Snap.Hutao/Resource/Localization/SH.resx | 3 ++ .../Service/Cultivation/CultivationService.cs | 42 +++++++++++++++++++ .../Cultivation/ICultivationService.cs | 5 +++ .../CultivatePromotionDeltaBatchDialog.xaml | 5 +++ ...CultivatePromotionDeltaBatchDialog.xaml.cs | 3 +- .../Dialog/CultivatePromotionDeltaOptions.cs | 8 +++- .../AvatarProperty/AvatarPropertyViewModel.cs | 10 ++++- ...CultivationProjectEntriesChangedMessage.cs | 16 +++++++ .../Cultivation/CultivationViewModel.cs | 14 ++++++- 10 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs 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 4b1c543..3c70037 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx @@ -1349,6 +1349,9 @@ Space Available: {2} Weapon Target Level + + Clear existing character and weapon entries before updating + Enter the plan name here diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index e8536bd..1701d4e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1391,6 +1391,9 @@ 武器目标等级 + + 更新计划前先清空已有角色与武器条目 + 在此处输入计划名称 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index bdffcd1..9a0e04d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -178,6 +178,48 @@ internal sealed partial class CultivationService : ICultivationService cultivationRepository.RemoveCultivateEntryById(entryId); } + public async ValueTask RemoveAvatarAndWeaponEntriesForCurrentProjectAsync() + { + IAdvancedDbCollectionView projects = await GetProjectCollectionAsync().ConfigureAwait(false); + if (!await EnsureCurrentProjectAsync(projects).ConfigureAwait(false)) + { + return; + } + + ArgumentNullException.ThrowIfNull(projects.CurrentItem); + Guid projectId = projects.CurrentItem.InnerId; + + await taskContext.SwitchToBackgroundAsync(); + + ImmutableArray entries = cultivationRepository.GetCultivateEntryImmutableArrayByProjectId(projectId); + List weaponEntryIds = new(entries.Length); + List avatarEntryIds = new(entries.Length); + foreach (ref readonly CultivateEntry entry in entries.AsSpan()) + { + switch (entry.Type) + { + case CultivateType.Weapon: + weaponEntryIds.Add(entry.InnerId); + break; + case CultivateType.AvatarAndSkill: + avatarEntryIds.Add(entry.InnerId); + break; + } + } + + foreach (Guid id in weaponEntryIds) + { + cultivationRepository.RemoveCultivateEntryById(id); + } + + foreach (Guid id in avatarEntryIds) + { + cultivationRepository.RemoveCultivateEntryById(id); + } + + entryCollectionCache.TryRemove(projectId, out _); + } + public void SaveCultivateItem(CultivateItemView item) { cultivationRepository.UpdateCultivateItem(item.Entity); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index ee00ee3..7daf318 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -23,6 +23,11 @@ internal interface ICultivationService ValueTask RemoveCultivateEntryAsync(Guid entryId); + /// + /// 移除当前选中养成计划中角色与武器类型的全部养成条目。 + /// + ValueTask RemoveAvatarAndWeaponEntriesForCurrentProjectAsync(); + ValueTask RemoveProjectAsync(CultivateProject project); ValueTask SaveConsumptionAsync(InputConsumption inputConsumption); diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml index 0d0e0e7..639212a 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml @@ -77,5 +77,10 @@ + + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs index a5d33ab..00aa35e 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs @@ -10,6 +10,7 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.UI.Xaml.View.Dialog; [DependencyProperty("PromotionDelta", NotNull = true, CreateDefaultValueCallbackName = nameof(CreatePromotionDeltaDefaultValue))] +[DependencyProperty("ClearAvatarAndWeaponEntriesBeforeSync", DefaultValue = false, NotNull = true)] internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog { private readonly IContentDialogFactory contentDialogFactory; @@ -45,7 +46,7 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog LocalSetting.Set(SettingKeys.CultivationWeapon90LevelTarget, weapon.LevelTarget); } - return new(true, new(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex)); + return new(true, new CultivatePromotionDeltaOptions(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex, ClearAvatarAndWeaponEntriesBeforeSync)); } private static object CreatePromotionDeltaDefaultValue() diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs index 621d687..f374f64 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Dialog/CultivatePromotionDeltaOptions.cs @@ -8,7 +8,7 @@ namespace Snap.Hutao.UI.Xaml.View.Dialog; internal sealed class CultivatePromotionDeltaOptions { - public CultivatePromotionDeltaOptions(AvatarPromotionDelta delta, ConsumptionSaveStrategyKind strategy) + public CultivatePromotionDeltaOptions(AvatarPromotionDelta delta, ConsumptionSaveStrategyKind strategy, bool clearAvatarAndWeaponEntriesBeforeSync = false) { delta.AvatarLevelTarget = delta.AvatarLevelTarget switch { @@ -19,9 +19,15 @@ internal sealed class CultivatePromotionDeltaOptions Delta = delta; Strategy = strategy; + ClearAvatarAndWeaponEntriesBeforeSync = clearAvatarAndWeaponEntriesBeforeSync; } public AvatarPromotionDelta Delta { get; } public ConsumptionSaveStrategyKind Strategy { get; } + + /// + /// 批量同步前是否清空当前计划中已有的角色与武器养成条目(不含家具等其它类型)。 + /// + public bool ClearAvatarAndWeaponEntriesBeforeSync { get; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs index a8c20a3..2aa581a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -21,6 +21,7 @@ using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.UI.Xaml.Control.AutoSuggestBox; using Snap.Hutao.UI.Xaml.View.Dialog; +using Snap.Hutao.ViewModel.Cultivation; using Snap.Hutao.ViewModel.User; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using System.Collections.Immutable; @@ -286,6 +287,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I BatchCultivateResult result = default; using (await scopeContext.ContentDialogFactory.BlockAsync(progressDialog).ConfigureAwait(false)) { + if (baseline.ClearAvatarAndWeaponEntriesBeforeSync) + { + await scopeContext.CultivationService.RemoveAvatarAndWeaponEntriesForCurrentProjectAsync().ConfigureAwait(false); + } + ImmutableArray.Builder deltasBuilder = ImmutableArray.CreateBuilder(); foreach (AvatarView avatar in targetAvatars) { @@ -305,7 +311,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I foreach ((CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) in batchConsumption.Items.Zip(deltas)) { - if (!await SaveCultivationAsync(consumption, new(delta, baseline.Strategy), true).ConfigureAwait(false)) + if (!await SaveCultivationAsync(consumption, new CultivatePromotionDeltaOptions(delta, baseline.Strategy), true).ConfigureAwait(false)) { break; } @@ -314,6 +320,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I } } + scopeContext.Messenger.Send(CultivationProjectEntriesChangedMessage.Empty); + InfoBarMessage message = result.SkippedCount > 0 ? InfoBarMessage.Warning(SH.FormatViewModelCultivationBatchAddIncompleted(result.SucceedCount, result.SkippedCount)) : InfoBarMessage.Success(SH.FormatViewModelCultivationBatchAddCompleted(result.SucceedCount, result.SkippedCount)); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs new file mode 100644 index 0000000..95f0804 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationProjectEntriesChangedMessage.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.ViewModel.Cultivation; + +/// +/// 当前养成计划的条目在 UI 外被批量变更(例如从「我的角色」同步)后,通知养成页刷新列表与统计。 +/// +internal sealed class CultivationProjectEntriesChangedMessage +{ + public static readonly CultivationProjectEntriesChangedMessage Empty = new(); + + private CultivationProjectEntriesChangedMessage() + { + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs index 8cd2574..e4d79e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core; using Snap.Hutao.Core.Database; @@ -29,7 +30,7 @@ namespace Snap.Hutao.ViewModel.Cultivation; [SuppressMessage("", "CA1001")] [BindableCustomPropertyProvider] [Service(ServiceLifetime.Scoped)] -internal sealed partial class CultivationViewModel : Abstraction.ViewModel +internal sealed partial class CultivationViewModel : Abstraction.ViewModel, IRecipient { private readonly ExclusiveTokenProvider exclusiveTokenProvider = new(); @@ -132,6 +133,17 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel UpdateEntryCollectionAsync(Projects?.CurrentItem).SafeForget(); } + public async void Receive(CultivationProjectEntriesChangedMessage message) + { + await taskContext.SwitchToMainThreadAsync(); + if (Projects?.CurrentItem is null) + { + return; + } + + await UpdateEntryCollectionAsync(Projects.CurrentItem).ConfigureAwait(false); + } + [Command("AddProjectCommand")] private async Task AddProjectAsync() {