diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.Designer.cs
new file mode 100644
index 0000000..942e0b3
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.Designer.cs
@@ -0,0 +1,670 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Snap.Hutao.Model.Entity.Database;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences")]
+ partial class AddCultivateProjectAvatarPropertyBatchPreferences
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ArchiveId")
+ .HasColumnType("TEXT");
+
+ b.Property("Current")
+ .HasColumnType("INTEGER");
+
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("Time")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ArchiveId");
+
+ b.ToTable("achievements");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("achievement_archives");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Info2")
+ .HasColumnType("TEXT");
+
+ b.Property("RefreshTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("avatar_infos");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarStrategy", b =>
+ {
+ b.Property("AvatarId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChineseStrategyId")
+ .HasColumnType("INTEGER");
+
+ b.Property("OverseaStrategyId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AvatarId");
+
+ b.ToTable("avatar_strategies");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProjectId")
+ .HasColumnType("TEXT");
+
+ b.Property("RelatedEntryId")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ProjectId");
+
+ b.HasIndex("RelatedEntryId");
+
+ b.ToTable("cultivate_entries");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AvatarIsPromoting")
+ .HasColumnType("INTEGER");
+
+ b.Property("AvatarLevelFrom")
+ .HasColumnType("INTEGER");
+
+ b.Property("AvatarLevelTo")
+ .HasColumnType("INTEGER");
+
+ b.Property("EntryId")
+ .HasColumnType("TEXT");
+
+ b.Property("SkillALevelFrom")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkillALevelTo")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkillELevelFrom")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkillELevelTo")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkillQLevelFrom")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkillQLevelTo")
+ .HasColumnType("INTEGER");
+
+ b.Property("WeaponIsPromoting")
+ .HasColumnType("INTEGER");
+
+ b.Property("WeaponLevelFrom")
+ .HasColumnType("INTEGER");
+
+ b.Property("WeaponLevelTo")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("EntryId")
+ .IsUnique();
+
+ b.ToTable("cultivate_entry_level_informations");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("EntryId")
+ .HasColumnType("TEXT");
+
+ b.Property("IsFinished")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("EntryId");
+
+ b.ToTable("cultivate_items");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AvatarPropertyBatchCultivatePreferencesJson")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ServerTimeZoneOffset")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("cultivate_projects");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("DailyNote")
+ .HasColumnType("TEXT");
+
+ b.Property("DailyTaskDotVisible")
+ .HasColumnType("INTEGER");
+
+ b.Property("DailyTaskNotify")
+ .HasColumnType("INTEGER");
+
+ b.Property("DailyTaskNotifySuppressed")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpeditionDotVisible")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpeditionNotify")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpeditionNotifySuppressed")
+ .HasColumnType("INTEGER");
+
+ b.Property("HomeCoinDotVisible")
+ .HasColumnType("INTEGER");
+
+ b.Property("HomeCoinNotifySuppressed")
+ .HasColumnType("INTEGER");
+
+ b.Property("HomeCoinNotifyThreshold")
+ .HasColumnType("INTEGER");
+
+ b.Property("RefreshTime")
+ .HasColumnType("TEXT");
+
+ b.Property("ResinDotVisible")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResinNotifySuppressed")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResinNotifyThreshold")
+ .HasColumnType("INTEGER");
+
+ b.Property("TransformerDotVisible")
+ .HasColumnType("INTEGER");
+
+ b.Property("TransformerNotify")
+ .HasColumnType("INTEGER");
+
+ b.Property("TransformerNotifySuppressed")
+ .HasColumnType("INTEGER");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("daily_notes");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("gacha_archives");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ArchiveId")
+ .HasColumnType("TEXT");
+
+ b.Property("GachaType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("INTEGER");
+
+ b.Property("QueryType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Time")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ArchiveId");
+
+ b.ToTable("gacha_items");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Index")
+ .HasColumnType("INTEGER");
+
+ b.Property("MacAddress")
+ .HasColumnType("TEXT");
+
+ b.Property("Mid")
+ .HasColumnType("TEXT");
+
+ b.Property("MihoyoSDK")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("game_accounts");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.HardChallengeEntry", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("HardChallengeData")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ScheduleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("hard_challenges");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("ItemId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProjectId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.HasIndex("ProjectId");
+
+ b.ToTable("inventory_items");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpireTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("object_cache");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.RoleCombatEntry", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("RoleCombatData")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ScheduleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("role_combats");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("settings");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ScheduleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SpiralAbyss")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("spiral_abysses");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.UidProfilePicture", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AvatarId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CostumeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfilePictureId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RefreshTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Uid")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("uid_profile_pictures");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
+ {
+ b.Property("InnerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Aid")
+ .HasColumnType("TEXT");
+
+ b.Property("CookieToken")
+ .HasColumnType("TEXT");
+
+ b.Property("CookieTokenLastUpdateTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Fingerprint")
+ .HasColumnType("TEXT");
+
+ b.Property("FingerprintLastUpdateTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Index")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsOversea")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsSelected")
+ .HasColumnType("INTEGER");
+
+ b.Property("LToken")
+ .HasColumnType("TEXT")
+ .HasColumnName("Ltoken");
+
+ b.Property("Mid")
+ .HasColumnType("TEXT");
+
+ b.Property("PreferredUid")
+ .HasColumnType("TEXT");
+
+ b.Property("SToken")
+ .HasColumnType("TEXT")
+ .HasColumnName("Stoken");
+
+ b.HasKey("InnerId");
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
+ .WithMany()
+ .HasForeignKey("ArchiveId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Archive");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
+ .WithMany()
+ .HasForeignKey("ProjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "RelatedEntry")
+ .WithMany()
+ .HasForeignKey("RelatedEntryId");
+
+ b.Navigation("Project");
+
+ b.Navigation("RelatedEntry");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
+ .WithOne("LevelInformation")
+ .HasForeignKey("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", "EntryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Entry");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
+ .WithMany()
+ .HasForeignKey("EntryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Entry");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
+ .WithMany()
+ .HasForeignKey("ArchiveId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Archive");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
+ {
+ b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
+ .WithMany()
+ .HasForeignKey("ProjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Project");
+ });
+
+ modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
+ {
+ b.Navigation("LevelInformation");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.cs
new file mode 100644
index 0000000..9e5a9bd
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20260504033049_AddCultivateProjectAvatarPropertyBatchPreferences.cs
@@ -0,0 +1,51 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Snap.Hutao.Migrations
+{
+ ///
+ public partial class AddCultivateProjectAvatarPropertyBatchPreferences : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_cultivate_entries_cultivate_entries_RelatedEntryId",
+ table: "cultivate_entries");
+
+ migrationBuilder.AddColumn(
+ name: "AvatarPropertyBatchCultivatePreferencesJson",
+ table: "cultivate_projects",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_cultivate_entries_cultivate_entries_RelatedEntryId",
+ table: "cultivate_entries",
+ column: "RelatedEntryId",
+ principalTable: "cultivate_entries",
+ principalColumn: "InnerId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_cultivate_entries_cultivate_entries_RelatedEntryId",
+ table: "cultivate_entries");
+
+ migrationBuilder.DropColumn(
+ name: "AvatarPropertyBatchCultivatePreferencesJson",
+ table: "cultivate_projects");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_cultivate_entries_cultivate_entries_RelatedEntryId",
+ table: "cultivate_entries",
+ column: "RelatedEntryId",
+ principalTable: "cultivate_entries",
+ principalColumn: "InnerId",
+ onDelete: ReferentialAction.SetNull);
+ }
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
index 58a702c..32b2000 100644
--- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs
@@ -212,6 +212,9 @@ namespace Snap.Hutao.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
+ b.Property("AvatarPropertyBatchCultivatePreferencesJson")
+ .HasColumnType("TEXT");
+
b.Property("IsSelected")
.HasColumnType("INTEGER");
@@ -592,8 +595,7 @@ namespace Snap.Hutao.Migrations
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "RelatedEntry")
.WithMany()
- .HasForeignKey("RelatedEntryId")
- .OnDelete(DeleteBehavior.SetNull);
+ .HasForeignKey("RelatedEntryId");
b.Navigation("Project");
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Cultivation/CultivateProjectAvatarPropertyBatchPreferences.cs b/src/Snap.Hutao/Snap.Hutao/Model/Cultivation/CultivateProjectAvatarPropertyBatchPreferences.cs
new file mode 100644
index 0000000..1f79c48
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Cultivation/CultivateProjectAvatarPropertyBatchPreferences.cs
@@ -0,0 +1,24 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Model.Cultivation;
+
+///
+/// 「我的角色」批量同步到当前养成计划时,在批量对话框中使用的目标等级、保存策略等;按 持久化。
+///
+internal sealed class CultivateProjectAvatarPropertyBatchPreferences
+{
+ public uint AvatarLevelTarget { get; set; }
+
+ public uint SkillATarget { get; set; }
+
+ public uint SkillETarget { get; set; }
+
+ public uint SkillQTarget { get; set; }
+
+ public uint WeaponLevelTarget { get; set; }
+
+ public int ConsumptionSaveStrategyIndex { get; set; }
+
+ public bool ClearAvatarAndWeaponEntriesBeforeSync { get; set; }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs
index 217c687..30ebde5 100644
--- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs
@@ -22,6 +22,11 @@ internal sealed partial class CultivateProject : ISelectable,
public TimeSpan ServerTimeZoneOffset { get; set; }
+ ///
+ /// 的 JSON,按项目记忆批量同步养成选项。
+ ///
+ public string? AvatarPropertyBatchCultivatePreferencesJson { get; set; }
+
public static CultivateProject From(string name, in TimeSpan serverTimeOffset)
{
return new()
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
index 9a0e04d..e8704e6 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
@@ -2,13 +2,16 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
+using Snap.Hutao.Core.Text.Json;
using Snap.Hutao.Model;
+using Snap.Hutao.Model.Cultivation;
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.Abstraction;
using Snap.Hutao.Service.Cultivation.Consumption;
using Snap.Hutao.Service.Inventory;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
@@ -20,6 +23,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text.Json;
using ModelItem = Snap.Hutao.Model.Item;
namespace Snap.Hutao.Service.Cultivation;
@@ -363,6 +367,50 @@ internal sealed partial class CultivationService : ICultivationService
return ProjectAddResultKind.Added;
}
+ public async ValueTask GetAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync()
+ {
+ IAdvancedDbCollectionView projects = await GetProjectCollectionAsync().ConfigureAwait(false);
+ if (!await EnsureCurrentProjectAsync(projects).ConfigureAwait(false))
+ {
+ return null;
+ }
+
+ string? json = projects.CurrentItem?.AvatarPropertyBatchCultivatePreferencesJson;
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ return null;
+ }
+
+ try
+ {
+ return JsonSerializer.Deserialize(json, JsonOptions.Default);
+ }
+ catch (JsonException)
+ {
+ return null;
+ }
+ }
+
+ public async ValueTask SaveAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync(CultivateProjectAvatarPropertyBatchPreferences preferences)
+ {
+ ArgumentNullException.ThrowIfNull(preferences);
+
+ IAdvancedDbCollectionView projects = await GetProjectCollectionAsync().ConfigureAwait(false);
+ if (!await EnsureCurrentProjectAsync(projects).ConfigureAwait(false))
+ {
+ return;
+ }
+
+ ArgumentNullException.ThrowIfNull(projects.CurrentItem);
+
+ CultivateProject project = projects.CurrentItem;
+
+ await taskContext.SwitchToBackgroundAsync();
+
+ project.AvatarPropertyBatchCultivatePreferencesJson = JsonSerializer.Serialize(preferences, JsonOptions.Default);
+ cultivationRepository.Update(project);
+ }
+
public async ValueTask RemoveProjectAsync(CultivateProject project)
{
ArgumentNullException.ThrowIfNull(projects);
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
index 7daf318..870dc85 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Database;
+using Snap.Hutao.Model.Cultivation;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Cultivation.Consumption;
using Snap.Hutao.ViewModel.Cultivation;
@@ -43,4 +44,14 @@ internal interface ICultivationService
void SaveCultivateItem(CultivateItemView item);
ValueTask TryAddProjectAsync(CultivateProject project);
+
+ ///
+ /// 读取当前选中养成计划下「我的角色」批量同步对话框的已保存选项;未保存时返回 。
+ ///
+ ValueTask GetAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync();
+
+ ///
+ /// 将批量同步对话框的选项写入当前选中养成计划。
+ ///
+ ValueTask SaveAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync(CultivateProjectAvatarPropertyBatchPreferences preferences);
}
\ 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 00aa35e..e08a810 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
@@ -4,6 +4,7 @@
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Factory.ContentDialog;
+using Snap.Hutao.Model.Cultivation;
using Snap.Hutao.Service.Cultivation.Consumption;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
@@ -15,8 +16,17 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog
{
private readonly IContentDialogFactory contentDialogFactory;
- [GeneratedConstructor(InitializeComponent = true)]
- public partial CultivatePromotionDeltaBatchDialog(IServiceProvider serviceProvider);
+ public CultivatePromotionDeltaBatchDialog(IServiceProvider serviceProvider, CultivateProjectAvatarPropertyBatchPreferences? initialPreferences = null)
+ {
+ InitializeComponent();
+
+ contentDialogFactory = serviceProvider.GetRequiredService();
+
+ if (initialPreferences is not null)
+ {
+ ApplyInitialPreferences(initialPreferences);
+ }
+ }
public async ValueTask> GetPromotionDeltaBaselineAsync()
{
@@ -49,8 +59,28 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog
return new(true, new CultivatePromotionDeltaOptions(PromotionDelta, (ConsumptionSaveStrategyKind)SaveModeSelector.SelectedIndex, ClearAvatarAndWeaponEntriesBeforeSync));
}
+ private void ApplyInitialPreferences(CultivateProjectAvatarPropertyBatchPreferences p)
+ {
+ PromotionDelta.AvatarLevelTarget = p.AvatarLevelTarget;
+
+ if (PromotionDelta.SkillList is [{ } a, { } e, { } q, ..])
+ {
+ a.LevelTarget = p.SkillATarget;
+ e.LevelTarget = p.SkillETarget;
+ q.LevelTarget = p.SkillQTarget;
+ }
+
+ if (PromotionDelta.Weapon is { } w)
+ {
+ w.LevelTarget = p.WeaponLevelTarget;
+ }
+
+ SaveModeSelector.SelectedIndex = int.Clamp(p.ConsumptionSaveStrategyIndex, 0, 2);
+ ClearAvatarAndWeaponEntriesBeforeSync = p.ClearAvatarAndWeaponEntriesBeforeSync;
+ }
+
private static object CreatePromotionDeltaDefaultValue()
{
return AvatarPromotionDelta.CreateForBaseline();
}
-}
\ 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 2aa581a..a2358dc 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs
@@ -9,6 +9,7 @@ using Snap.Hutao.Core.Setting;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Calculable;
+using Snap.Hutao.Model.Cultivation;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Service;
using Snap.Hutao.Service.AvatarInfo;
@@ -269,15 +270,27 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
targetAvatars = [.. avatars.Source];
}
- CultivatePromotionDeltaBatchDialog dialog = await scopeContext.ContentDialogFactory
- .CreateInstanceAsync(scopeContext.ServiceProvider)
+ CultivateProjectAvatarPropertyBatchPreferences? batchPrefs = await scopeContext.CultivationService
+ .GetAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync()
.ConfigureAwait(false);
+ CultivatePromotionDeltaBatchDialog dialog = batchPrefs is null
+ ? await scopeContext.ContentDialogFactory
+ .CreateInstanceAsync(scopeContext.ServiceProvider)
+ .ConfigureAwait(false)
+ : await scopeContext.ContentDialogFactory
+ .CreateInstanceAsync(scopeContext.ServiceProvider, batchPrefs)
+ .ConfigureAwait(false);
+
if (await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false) is not (true, { } baseline))
{
return;
}
+ await scopeContext.CultivationService
+ .SaveAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync(ToAvatarPropertyBatchPreferences(baseline))
+ .ConfigureAwait(false);
+
ArgumentNullException.ThrowIfNull(baseline.Delta.Weapon);
ContentDialog progressDialog = await scopeContext.ContentDialogFactory
@@ -329,6 +342,31 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
scopeContext.Messenger.Send(message);
}
+ private static CultivateProjectAvatarPropertyBatchPreferences ToAvatarPropertyBatchPreferences(CultivatePromotionDeltaOptions baseline)
+ {
+ CalculatorAvatarPromotionDelta d = baseline.Delta;
+ uint sa = 10;
+ uint se = 10;
+ uint sq = 10;
+ if (d.SkillList is [{ } a, { } e, { } q, ..])
+ {
+ sa = a.LevelTarget;
+ se = e.LevelTarget;
+ sq = q.LevelTarget;
+ }
+
+ return new CultivateProjectAvatarPropertyBatchPreferences
+ {
+ AvatarLevelTarget = d.AvatarLevelTarget,
+ SkillATarget = sa,
+ SkillETarget = se,
+ SkillQTarget = sq,
+ WeaponLevelTarget = d.Weapon?.LevelTarget ?? 90U,
+ ConsumptionSaveStrategyIndex = (int)baseline.Strategy,
+ ClearAvatarAndWeaponEntriesBeforeSync = baseline.ClearAvatarAndWeaponEntriesBeforeSync,
+ };
+ }
+
/// if we can continue saving consumptions, otherwise .
private async ValueTask SaveCultivationAsync(CalculatorConsumption consumption, CultivatePromotionDeltaOptions options, bool isBatch = false)
{