diff --git a/LanMountainDesktop.Launcher/Services/DataLocationOobeStep.cs b/LanMountainDesktop.Launcher/Services/DataLocationOobeStep.cs index 1abf73d..67e40a2 100644 --- a/LanMountainDesktop.Launcher/Services/DataLocationOobeStep.cs +++ b/LanMountainDesktop.Launcher/Services/DataLocationOobeStep.cs @@ -43,11 +43,11 @@ internal sealed class DataLocationOobeStep : IOobeStep if (result is null) { Logger.Info("DataLocation OOBE step: user cancelled or closed window. Using default system location."); - _resolver.ApplyLocationChoice(DataLocationMode.System, false); + _resolver.ApplyLocationChoice(DataLocationMode.System, null, false); } else { - var success = _resolver.ApplyLocationChoice(result.SelectedMode, result.MigrateExistingData); + var success = _resolver.ApplyLocationChoice(result.SelectedMode, null, result.MigrateExistingData); Logger.Info( $"DataLocation OOBE step: user selected '{result.SelectedMode}'. " + $"Migrate={result.MigrateExistingData}; Success={success}."); diff --git a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs index ae82021..860b884 100644 --- a/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs +++ b/LanMountainDesktop.Launcher/Services/DataLocationResolver.cs @@ -6,16 +6,15 @@ namespace LanMountainDesktop.Launcher.Services; internal sealed class DataLocationResolver { private const string ConfigFileName = "data-location.config.json"; - private const string PortableDataFolderName = "AppData"; + private const string LauncherFolderName = "Launcher"; + private const string DesktopFolderName = "Desktop"; private readonly string _appRoot; - private readonly string _configPath; private readonly string _defaultSystemDataPath; public DataLocationResolver(string appRoot) { _appRoot = Path.GetFullPath(appRoot); - _configPath = Path.Combine(_appRoot, ConfigFileName); _defaultSystemDataPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LanMountainDesktop"); @@ -23,12 +22,19 @@ internal sealed class DataLocationResolver public string AppRoot => _appRoot; - public string ConfigPath => _configPath; - + /// + /// 默认系统数据路径(用户目录) + /// public string DefaultSystemDataPath => _defaultSystemDataPath; - public string DefaultPortableDataPath => Path.Combine(_appRoot, PortableDataFolderName); + /// + /// 默认便携模式数据路径(应用目录下的 AppData) + /// + public string DefaultPortableDataPath => Path.Combine(_appRoot, "AppData"); + /// + /// 检查是否允许便携模式(应用目录是否可写) + /// public bool IsPortableModeAllowed() { try @@ -44,40 +50,9 @@ internal sealed class DataLocationResolver } } - public DataLocationConfig? LoadConfig() - { - try - { - if (!File.Exists(_configPath)) - { - return null; - } - - var json = File.ReadAllText(_configPath); - return JsonSerializer.Deserialize(json, AppJsonContext.Default.DataLocationConfig); - } - catch (Exception ex) - { - Logger.Warn($"Failed to load data location config from '{_configPath}'. Error='{ex.Message}'."); - return null; - } - } - - public bool SaveConfig(DataLocationConfig config) - { - try - { - var json = JsonSerializer.Serialize(config, AppJsonContext.Default.DataLocationConfig); - File.WriteAllText(_configPath, json); - return true; - } - catch (Exception ex) - { - Logger.Warn($"Failed to save data location config to '{_configPath}'. Error='{ex.Message}'."); - return false; - } - } - + /// + /// 解析数据根目录(用户选择的位置) + /// public string ResolveDataRoot() { var config = LoadConfig(); @@ -90,7 +65,7 @@ internal sealed class DataLocationResolver { var portablePath = !string.IsNullOrWhiteSpace(config.PortableDataPath) ? config.PortableDataPath - : DefaultPortableDataPath; + : _defaultSystemDataPath; return Path.GetFullPath(portablePath); } @@ -99,6 +74,46 @@ internal sealed class DataLocationResolver : _defaultSystemDataPath; } + /// + /// 启动器数据目录(日志、配置、状态等) + /// + public string ResolveLauncherDataPath() + { + return Path.Combine(ResolveDataRoot(), LauncherFolderName); + } + + /// + /// 桌面应用数据目录(组件、设置、插件等) + /// + public string ResolveDesktopDataPath() + { + return Path.Combine(ResolveDataRoot(), DesktopFolderName); + } + + /// + /// 数据位置配置文件路径(保存在 Launcher 目录下) + /// + public string ResolveConfigPath() + { + return Path.Combine(ResolveLauncherDataPath(), ConfigFileName); + } + + /// + /// 启动器日志目录 + /// + public string ResolveLauncherLogsPath() + { + return Path.Combine(ResolveLauncherDataPath(), "logs"); + } + + /// + /// 启动器状态目录 + /// + public string ResolveLauncherStatePath() + { + return Path.Combine(ResolveLauncherDataPath(), "state"); + } + public DataLocationMode ResolveMode() { var config = LoadConfig(); @@ -112,116 +127,127 @@ internal sealed class DataLocationResolver : DataLocationMode.System; } + public DataLocationConfig? LoadConfig() + { + try + { + var configPath = ResolveConfigPath(); + if (!File.Exists(configPath)) + { + return null; + } + + var json = File.ReadAllText(configPath); + return JsonSerializer.Deserialize(json, AppJsonContext.Default.DataLocationConfig); + } + catch (Exception ex) + { + Logger.Warn($"Failed to load data location config. Error='{ex.Message}'."); + return null; + } + } + + public bool SaveConfig(DataLocationConfig config) + { + try + { + var launcherPath = ResolveLauncherDataPath(); + Directory.CreateDirectory(launcherPath); + + var configPath = ResolveConfigPath(); + var json = JsonSerializer.Serialize(config, AppJsonContext.Default.DataLocationConfig); + File.WriteAllText(configPath, json); + return true; + } + catch (Exception ex) + { + Logger.Warn($"Failed to save data location config. Error='{ex.Message}'."); + return false; + } + } + + public bool ApplyLocationChoice(DataLocationMode mode, string? customPath = null, bool migrateExistingData = false) + { + var targetDataRoot = mode == DataLocationMode.Portable && !string.IsNullOrWhiteSpace(customPath) + ? Path.GetFullPath(customPath) + : _defaultSystemDataPath; + + var config = new DataLocationConfig + { + DataLocationMode = mode.ToString(), + SystemDataPath = _defaultSystemDataPath, + PortableDataPath = mode == DataLocationMode.Portable ? targetDataRoot : null + }; + + // 先创建目录结构 + try + { + Directory.CreateDirectory(ResolveLauncherDataPath()); + Directory.CreateDirectory(ResolveDesktopDataPath()); + } + catch (Exception ex) + { + Logger.Warn($"Failed to create data directories. Error='{ex.Message}'."); + return false; + } + + // 保存配置 + if (!SaveConfig(config)) + { + return false; + } + + if (migrateExistingData && mode == DataLocationMode.Portable) + { + MigrateSystemDataToPortable(targetDataRoot); + } + + return true; + } + public bool HasExistingSystemData() { - var systemPath = _defaultSystemDataPath; - if (!Directory.Exists(systemPath)) + var desktopPath = Path.Combine(_defaultSystemDataPath, DesktopFolderName); + if (!Directory.Exists(desktopPath)) { return false; } var markerFiles = new[] { - Path.Combine(systemPath, "settings.json"), - Path.Combine(systemPath, "launcher-settings.json"), - Path.Combine(systemPath, "component-state.db"), - Path.Combine(systemPath, "app.db") + Path.Combine(desktopPath, "settings.json"), + Path.Combine(desktopPath, "component-state.db"), + Path.Combine(desktopPath, "app.db") }; return markerFiles.Any(File.Exists); } - public bool ApplyLocationChoice(DataLocationMode mode, bool migrateExistingData) - { - var config = new DataLocationConfig - { - DataLocationMode = mode.ToString(), - SystemDataPath = _defaultSystemDataPath, - PortableDataPath = DefaultPortableDataPath - }; - - if (!SaveConfig(config)) - { - return false; - } - - var targetDataRoot = mode == DataLocationMode.Portable - ? DefaultPortableDataPath - : _defaultSystemDataPath; - - try - { - Directory.CreateDirectory(targetDataRoot); - } - catch (Exception ex) - { - Logger.Warn($"Failed to create data directory '{targetDataRoot}'. Error='{ex.Message}'."); - return false; - } - - if (migrateExistingData && mode == DataLocationMode.Portable) - { - MigrateSystemDataToPortable(); - } - - return true; - } - - private void MigrateSystemDataToPortable() + private void MigrateSystemDataToPortable(string targetDataRoot) { if (!HasExistingSystemData()) { return; } - var sourcePath = _defaultSystemDataPath; - var targetPath = DefaultPortableDataPath; + var sourceDesktopPath = Path.Combine(_defaultSystemDataPath, DesktopFolderName); + var targetDesktopPath = Path.Combine(targetDataRoot, DesktopFolderName); try { - Directory.CreateDirectory(targetPath); + Directory.CreateDirectory(targetDesktopPath); - var filesToMigrate = Directory.GetFiles(sourcePath, "*", SearchOption.TopDirectoryOnly); - foreach (var file in filesToMigrate) + // 迁移桌面数据 + if (Directory.Exists(sourceDesktopPath)) { - var fileName = Path.GetFileName(file); - var destFile = Path.Combine(targetPath, fileName); - try - { - File.Copy(file, destFile, overwrite: true); - } - catch (Exception ex) - { - Logger.Warn($"Failed to migrate file '{fileName}'. Error='{ex.Message}'."); - } + CopyDirectory(sourceDesktopPath, targetDesktopPath); } - var dirsToMigrate = Directory.GetDirectories(sourcePath, "*", SearchOption.TopDirectoryOnly); - foreach (var dir in dirsToMigrate) - { - var dirName = Path.GetFileName(dir); - if (string.Equals(dirName, ".launcher", StringComparison.OrdinalIgnoreCase) && - string.Equals(Path.GetFileName(sourcePath), "LanMountainDesktop", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var destDir = Path.Combine(targetPath, dirName); - try - { - CopyDirectory(dir, destDir); - } - catch (Exception ex) - { - Logger.Warn($"Failed to migrate directory '{dirName}'. Error='{ex.Message}'."); - } - } - - Logger.Info($"Data migration completed. Source='{sourcePath}'; Target='{targetPath}'."); + Logger.Info($"Data migration completed. Target='{targetDataRoot}'."); } catch (Exception ex) { - Logger.Warn($"Data migration failed. Source='{sourcePath}'; Target='{targetPath}'. Error='{ex.Message}'."); + Logger.Warn($"Data migration failed. Target='{targetDataRoot}'. Error='{ex.Message}'."); } } diff --git a/LanMountainDesktop.Launcher/Services/DeploymentLocator.cs b/LanMountainDesktop.Launcher/Services/DeploymentLocator.cs index 15e4c85..7ba3430 100644 --- a/LanMountainDesktop.Launcher/Services/DeploymentLocator.cs +++ b/LanMountainDesktop.Launcher/Services/DeploymentLocator.cs @@ -497,7 +497,8 @@ internal sealed class DeploymentLocator } // 3. 淇濈暀鏈夊揩鐓х殑鐗堟湰锛堢敤浜庡洖婊氾級 - var snapshotDir = Path.Combine(_appRoot, ".launcher", "snapshots"); + var resolver = new DataLocationResolver(_appRoot); + var snapshotDir = Path.Combine(resolver.ResolveLauncherDataPath(), "snapshots"); if (Directory.Exists(snapshotDir)) { try diff --git a/LanMountainDesktop.Launcher/Services/LauncherBackgroundService.cs b/LanMountainDesktop.Launcher/Services/LauncherBackgroundService.cs new file mode 100644 index 0000000..d39f4ad --- /dev/null +++ b/LanMountainDesktop.Launcher/Services/LauncherBackgroundService.cs @@ -0,0 +1,174 @@ +using Avalonia.Media.Imaging; + +namespace LanMountainDesktop.Launcher.Services; + +/// +/// 启动器背景图片服务 +/// +internal static class LauncherBackgroundService +{ + private const string PictureFileName = "Launcher Picture"; + private const long MaxFileSize = 10 * 1024 * 1024; // 10MB + private const double WindowAspectRatio = 7.0 / 5.0; // 700:500 + private const double AspectRatioTolerance = 0.15; // 15% 误差 + + private static Bitmap? _cachedBitmap; + private static string? _cachedPath; + + /// + /// 背景图片信息 + /// + public record BackgroundImageInfo + { + public required bool Exists { get; init; } + public required bool IsValid { get; init; } + public string? FilePath { get; init; } + public Bitmap? Bitmap { get; init; } + public int Width { get; init; } + public int Height { get; init; } + public double AspectRatio { get; init; } + public string? ErrorMessage { get; init; } + } + + /// + /// 加载背景图片 + /// + public static BackgroundImageInfo LoadBackgroundImage() + { + try + { + var resolver = new DataLocationResolver(AppContext.BaseDirectory); + var launcherPath = resolver.ResolveLauncherDataPath(); + + // 查找图片文件 + var imagePath = FindImageFile(launcherPath); + if (imagePath == null) + { + return new BackgroundImageInfo + { + Exists = false, + IsValid = false, + ErrorMessage = "未找到背景图片文件" + }; + } + + // 检查文件大小 + var fileInfo = new FileInfo(imagePath); + if (fileInfo.Length > MaxFileSize) + { + return new BackgroundImageInfo + { + Exists = true, + IsValid = false, + FilePath = imagePath, + ErrorMessage = $"图片文件过大 ({fileInfo.Length / 1024 / 1024}MB > 10MB)" + }; + } + + // 使用缓存 + if (_cachedBitmap != null && _cachedPath == imagePath) + { + return new BackgroundImageInfo + { + Exists = true, + IsValid = true, + FilePath = imagePath, + Bitmap = _cachedBitmap, + Width = _cachedBitmap.PixelSize.Width, + Height = _cachedBitmap.PixelSize.Height, + AspectRatio = (double)_cachedBitmap.PixelSize.Width / _cachedBitmap.PixelSize.Height + }; + } + + // 加载图片 + var bitmap = new Bitmap(imagePath); + var width = bitmap.PixelSize.Width; + var height = bitmap.PixelSize.Height; + var aspectRatio = (double)width / height; + + // 校验比例 + var ratioDiff = Math.Abs(aspectRatio - WindowAspectRatio) / WindowAspectRatio; + if (ratioDiff > AspectRatioTolerance) + { + bitmap.Dispose(); + return new BackgroundImageInfo + { + Exists = true, + IsValid = false, + FilePath = imagePath, + Width = width, + Height = height, + AspectRatio = aspectRatio, + ErrorMessage = $"图片比例不符合要求 ({aspectRatio:F2},需要接近 {WindowAspectRatio:F2})" + }; + } + + // 缓存图片 + _cachedBitmap = bitmap; + _cachedPath = imagePath; + + Logger.Info($"[LauncherBackground] 背景图片加载成功: {imagePath} ({width}x{height}, 比例: {aspectRatio:F2})"); + + return new BackgroundImageInfo + { + Exists = true, + IsValid = true, + FilePath = imagePath, + Bitmap = bitmap, + Width = width, + Height = height, + AspectRatio = aspectRatio + }; + } + catch (Exception ex) + { + Logger.Warn($"[LauncherBackground] 加载背景图片失败: {ex.Message}"); + return new BackgroundImageInfo + { + Exists = false, + IsValid = false, + ErrorMessage = $"加载失败: {ex.Message}" + }; + } + } + + /// + /// 查找图片文件 + /// + private static string? FindImageFile(string directory) + { + var extensions = new[] { ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp" }; + + foreach (var ext in extensions) + { + var path = Path.Combine(directory, PictureFileName + ext); + if (File.Exists(path)) + { + return path; + } + } + + // 也尝试不带扩展名的匹配(如果文件本身就有扩展名) + var files = Directory.GetFiles(directory, PictureFileName + ".*"); + foreach (var file in files) + { + var ext = Path.GetExtension(file).ToLowerInvariant(); + if (extensions.Contains(ext)) + { + return file; + } + } + + return null; + } + + /// + /// 清除缓存 + /// + public static void ClearCache() + { + _cachedBitmap?.Dispose(); + _cachedBitmap = null; + _cachedPath = null; + } +} diff --git a/LanMountainDesktop.Launcher/Services/LauncherDebugSettingsStore.cs b/LanMountainDesktop.Launcher/Services/LauncherDebugSettingsStore.cs index 6ea9d68..9ed4cad 100644 --- a/LanMountainDesktop.Launcher/Services/LauncherDebugSettingsStore.cs +++ b/LanMountainDesktop.Launcher/Services/LauncherDebugSettingsStore.cs @@ -104,7 +104,7 @@ internal static class LauncherDebugSettingsStore { var appRoot = Commands.ResolveAppRoot(CommandContext.FromArgs([])); var resolver = new DataLocationResolver(appRoot); - return Path.Combine(resolver.ResolveDataRoot(), ".launcher"); + return resolver.ResolveLauncherDataPath(); } catch { @@ -115,7 +115,7 @@ internal static class LauncherDebugSettingsStore var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); if (!string.IsNullOrWhiteSpace(appData)) { - return Path.Combine(appData, "LanMountainDesktop", ".launcher"); + return Path.Combine(appData, "LanMountainDesktop", "Launcher"); } } catch @@ -124,11 +124,11 @@ internal static class LauncherDebugSettingsStore try { - return Path.Combine(AppContext.BaseDirectory, ".launcher"); + return Path.Combine(AppContext.BaseDirectory, "Launcher"); } catch { - return Path.Combine(Directory.GetCurrentDirectory(), ".launcher"); + return Path.Combine(Directory.GetCurrentDirectory(), "Launcher"); } } } diff --git a/LanMountainDesktop.Launcher/Services/Logger.cs b/LanMountainDesktop.Launcher/Services/Logger.cs index 0e474a9..a76e987 100644 --- a/LanMountainDesktop.Launcher/Services/Logger.cs +++ b/LanMountainDesktop.Launcher/Services/Logger.cs @@ -57,7 +57,7 @@ internal static class Logger { var appRoot = Commands.ResolveAppRoot(CommandContext.FromArgs([])); var resolver = new DataLocationResolver(appRoot); - return Path.Combine(resolver.ResolveDataRoot(), ".launcher", "logs"); + return resolver.ResolveLauncherLogsPath(); } catch { @@ -68,7 +68,7 @@ internal static class Logger var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); if (!string.IsNullOrEmpty(appData)) { - return Path.Combine(appData, "LanMountainDesktop", ".launcher", "logs"); + return Path.Combine(appData, "LanMountainDesktop", "Launcher", "logs"); } } catch @@ -78,7 +78,7 @@ internal static class Logger try { var launcherDir = AppContext.BaseDirectory; - return Path.Combine(launcherDir, ".launcher", "logs"); + return Path.Combine(launcherDir, "Launcher", "logs"); } catch { diff --git a/LanMountainDesktop.Launcher/Services/OobeStateService.cs b/LanMountainDesktop.Launcher/Services/OobeStateService.cs index 39c8f03..9ecca79 100644 --- a/LanMountainDesktop.Launcher/Services/OobeStateService.cs +++ b/LanMountainDesktop.Launcher/Services/OobeStateService.cs @@ -23,7 +23,7 @@ internal sealed class OobeStateService var stateRoot = string.IsNullOrWhiteSpace(stateRootOverride) ? ResolveStateRoot(appRoot) : Path.GetFullPath(stateRootOverride); - _stateDirectory = Path.Combine(stateRoot, ".launcher", "state"); + _stateDirectory = Path.Combine(stateRoot, "Launcher", "state"); _statePath = Path.Combine(_stateDirectory, "oobe-state.json"); _legacyMarkerPath = Path.Combine(_stateDirectory, "first_run_completed"); } diff --git a/LanMountainDesktop.Launcher/Services/StartupAttemptRegistry.cs b/LanMountainDesktop.Launcher/Services/StartupAttemptRegistry.cs index 6291c58..c9ebd30 100644 --- a/LanMountainDesktop.Launcher/Services/StartupAttemptRegistry.cs +++ b/LanMountainDesktop.Launcher/Services/StartupAttemptRegistry.cs @@ -26,14 +26,14 @@ internal sealed class StartupAttemptRegistry { var appRoot = Commands.ResolveAppRoot(CommandContext.FromArgs([])); var resolver = new DataLocationResolver(appRoot); - return Path.Combine(resolver.ResolveDataRoot(), ".launcher", "state", "startup-attempt.json"); + return Path.Combine(resolver.ResolveLauncherStatePath(), "startup-attempt.json"); } catch { return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LanMountainDesktop", - ".launcher", + "Launcher", "state", "startup-attempt.json"); } diff --git a/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs b/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs index 7c008d7..727a117 100644 --- a/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs +++ b/LanMountainDesktop.Launcher/Services/UpdateEngineService.cs @@ -7,7 +7,6 @@ namespace LanMountainDesktop.Launcher.Services; internal sealed class UpdateEngineService { - private const string LauncherDirectoryName = ".launcher"; private const string UpdateDirectoryName = "update"; private const string IncomingDirectoryName = "incoming"; private const string SnapshotsDirectoryName = "snapshots"; @@ -22,7 +21,6 @@ internal sealed class UpdateEngineService private readonly DeploymentLocator _deploymentLocator; private readonly string _appRoot; - private readonly string _dataRoot; private readonly string _launcherRoot; private readonly string _incomingRoot; private readonly string _snapshotsRoot; @@ -31,8 +29,8 @@ internal sealed class UpdateEngineService { _deploymentLocator = deploymentLocator; _appRoot = deploymentLocator.GetAppRoot(); - _dataRoot = new DataLocationResolver(_appRoot).ResolveDataRoot(); - _launcherRoot = Path.Combine(_dataRoot, LauncherDirectoryName); + var resolver = new DataLocationResolver(_appRoot); + _launcherRoot = resolver.ResolveLauncherDataPath(); _incomingRoot = Path.Combine(_launcherRoot, UpdateDirectoryName, IncomingDirectoryName); _snapshotsRoot = Path.Combine(_launcherRoot, SnapshotsDirectoryName); } diff --git a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml index 313e442..b8b2f48 100644 --- a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml +++ b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml @@ -23,7 +23,6 @@ - diff --git a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml.cs b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml.cs index ceff7e7..8fc5eae 100644 --- a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml.cs +++ b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml.cs @@ -431,7 +431,7 @@ public partial class OobeWindow : Window // 应用数据位置选择 if (!_isDebugMode) { - _resolver.ApplyLocationChoice(_selectedDataLocationMode, _migrateExistingData); + _resolver.ApplyLocationChoice(_selectedDataLocationMode, null, _migrateExistingData); } await NavigateToStep(4); diff --git a/LanMountainDesktop.Launcher/Views/SplashWindow.axaml b/LanMountainDesktop.Launcher/Views/SplashWindow.axaml index 729bcc8..21bac27 100644 --- a/LanMountainDesktop.Launcher/Views/SplashWindow.axaml +++ b/LanMountainDesktop.Launcher/Views/SplashWindow.axaml @@ -20,8 +20,20 @@ - + + + + + + + ("VersionTextBorder") is { } versionBorder) { versionBorder.PointerPressed += OnVersionTextClick; } } + private void InitializeBackgroundImage() + { + try + { + var imageInfo = LauncherBackgroundService.LoadBackgroundImage(); + if (imageInfo is { IsValid: true, Bitmap: not null }) + { + if (this.FindControl("BackgroundImage") is { } backgroundImage) + { + backgroundImage.Source = imageInfo.Bitmap; + backgroundImage.IsVisible = true; + backgroundImage.Opacity = 1; + } + Logger.Info("[SplashWindow] 背景图片加载成功"); + } + else if (imageInfo is { Exists: true, IsValid: false }) + { + Logger.Warn($"[SplashWindow] 背景图片校验失败: {imageInfo.ErrorMessage}"); + } + } + catch (Exception ex) + { + Logger.Warn($"[SplashWindow] 加载背景图片失败: {ex.Message}"); + } + } + private async void OnWindowOpened(object? sender, EventArgs e) { if (_isOpened)