# 开机自启动功能实施计划 > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 为 YZWater3 添加跨平台开机自启动功能,用户可在参数设置页面切换开关。 **Architecture:** 创建 `StartupService` 静态类,通过 `RuntimeInformation.IsOSPlatform()` 判断平台,分别操作 Windows 注册表、Linux `.desktop` 文件、macOS LaunchAgent。在 `SystemConfig` 中新增 `RunAtStartup` 属性持久化到 JSON,在 `ViewBViewModel` 中添加属性和切换逻辑,在 `ViewBView.axaml` 中添加 ToggleSwitch。 **Tech Stack:** .NET 8, Avalonia 12, CommunityToolkit.Mvvm, Microsoft.Win32.Registry **Spec:** `docs/superpowers/specs/2026-09-01-autostart-design.md` --- ## 文件结构 | 操作 | 文件路径 | 职责 | |------|----------|------| | 新建 | `src/YZWater.Core/Services/StartupService.cs` | 跨平台开机自启动管理 | | 修改 | `src/YZWater.Core/Models/SystemConfig.cs` | 新增 `RunAtStartup` 属性 | | 修改 | `src/YZWater.Core/ViewModels/ViewBViewModel.cs` | 新增 `RunAtStartup` 属性 + 保存逻辑 + 修复 SaveConfig bug | | 修改 | `src/YZWater.Avalonia/Views/ViewBView.axaml` | 新增系统设置区块 + ToggleSwitch | | 修改 | `src/YZWater.Core/Services/LanguageService.cs` | 新增中英文 i18n 键 | | 修改 | `src/YZWater.Core/YZWater.Core.csproj` | 添加 `Microsoft.Win32.Registry` 包引用 | --- ## Task 1: 添加 NuGet 包引用 **Files:** - Modify: `src/YZWater.Core/YZWater.Core.csproj:10-23` - [ ] **Step 1: 添加 Microsoft.Win32.Registry 包** 在 `` 的 PackageReference 列表中添加: ```xml ``` 添加到 `Serilog.Sinks.File` 之后(第 20 行后)。 - [ ] **Step 2: 还原依赖** Run: `dotnet restore src/YZWater.Core` Expected: 成功还原,无错误 - [ ] **Step 3: 提交** ```bash git add src/YZWater.Core/YZWater.Core.csproj git commit -m "chore: 添加 Microsoft.Win32.Registry 包引用" ``` --- ## Task 2: SystemConfig 模型新增属性 **Files:** - Modify: `src/YZWater.Core/Models/SystemConfig.cs:130-141` - [ ] **Step 1: 在 `_plcPollingIntervalMs` 字段后添加新属性** 在第 141 行(`private int _plcPollingIntervalMs = 1000;`)之后,添加: ```csharp /// /// 是否开机自启动 /// [ObservableProperty] private bool _runAtStartup; ``` - [ ] **Step 2: 构建验证** Run: `dotnet build src/YZWater.Core` Expected: 编译成功 - [ ] **Step 3: 提交** ```bash git add src/YZWater.Core/Models/SystemConfig.cs git commit -m "feat: SystemConfig 新增 RunAtStartup 属性" ``` --- ## Task 3: 创建 StartupService **Files:** - Create: `src/YZWater.Core/Services/StartupService.cs` - [ ] **Step 1: 创建 StartupService.cs** 创建文件 `src/YZWater.Core/Services/StartupService.cs`,内容如下: ```csharp using System.Runtime.InteropServices; using Microsoft.Win32; using Serilog; namespace YZWater.Core.Services; /// /// 跨平台开机自启动管理服务 /// public static class StartupService { private const string AppName = "YZWater"; private const string WindowsRegistryPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; private const string LinuxDesktopFileName = "yzwater.desktop"; private const string MacPlistFileName = "com.yxwater.yzwater.plist"; /// /// 设置开机自启动 /// public static bool SetAutoStart(bool enabled) { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return SetWindowsAutoStart(enabled); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return SetLinuxAutoStart(enabled); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return SetMacAutoStart(enabled); Log.Warning("StartupService: 不支持的操作系统"); return false; } catch (Exception ex) { Log.Error(ex, "StartupService: 设置开机自启动失败 (enabled={Enabled})", enabled); return false; } } /// /// 检查是否已启用开机自启动 /// public static bool IsAutoStartEnabled() { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return IsWindowsAutoStartEnabled(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return IsLinuxAutoStartEnabled(); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return IsMacAutoStartEnabled(); return false; } catch (Exception ex) { Log.Error(ex, "StartupService: 检查开机自启动状态失败"); return false; } } /// /// 获取当前可执行文件路径 /// private static string GetExecutablePath() { return Environment.ProcessPath ?? Environment.GetCommandLineArgs()[0]; } #region Windows private static bool SetWindowsAutoStart(bool enabled) { using var key = Registry.CurrentUser.OpenSubKey(WindowsRegistryPath, true); if (key == null) { Log.Error("StartupService: 无法打开注册表路径 {Path}", WindowsRegistryPath); return false; } if (enabled) { key.SetValue(AppName, GetExecutablePath()); Log.Information("StartupService: Windows 开机自启动已启用"); } else { key.DeleteValue(AppName, false); Log.Information("StartupService: Windows 开机自启动已禁用"); } return true; } private static bool IsWindowsAutoStartEnabled() { using var key = Registry.CurrentUser.OpenSubKey(WindowsRegistryPath); return key?.GetValue(AppName) != null; } #endregion #region Linux private static string GetLinuxDesktopFilePath() { var configDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "autostart"); return Path.Combine(configDir, LinuxDesktopFileName); } private static bool SetLinuxAutoStart(bool enabled) { var filePath = GetLinuxDesktopFilePath(); if (enabled) { var dir = Path.GetDirectoryName(filePath)!; Directory.CreateDirectory(dir); var content = $""" [Desktop Entry] Type=Application Name={AppName} Exec={GetExecutablePath()} X-GNOME-Autostart-enabled=true """; File.WriteAllText(filePath, content); Log.Information("StartupService: Linux 开机自启动已启用 ({Path})", filePath); } else { if (File.Exists(filePath)) File.Delete(filePath); Log.Information("StartupService: Linux 开机自启动已禁用"); } return true; } private static bool IsLinuxAutoStartEnabled() { return File.Exists(GetLinuxDesktopFilePath()); } #endregion #region macOS private static string GetMacPlistPath() { var libraryDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "LaunchAgents"); return Path.Combine(libraryDir, MacPlistFileName); } private static bool SetMacAutoStart(bool enabled) { var filePath = GetMacPlistPath(); if (enabled) { var dir = Path.GetDirectoryName(filePath)!; Directory.CreateDirectory(dir); var content = $""" Label com.yxwater.yzwater ProgramArguments {GetExecutablePath()} RunAtLoad """; File.WriteAllText(filePath, content); Log.Information("StartupService: macOS 开机自启动已启用 ({Path})", filePath); } else { if (File.Exists(filePath)) File.Delete(filePath); Log.Information("StartupService: macOS 开机自启动已禁用"); } return true; } private static bool IsMacAutoStartEnabled() { return File.Exists(GetMacPlistPath()); } #endregion } ``` - [ ] **Step 2: 构建验证** Run: `dotnet build src/YZWater.Core` Expected: 编译成功 - [ ] **Step 3: 提交** ```bash git add src/YZWater.Core/Services/StartupService.cs git commit -m "feat: 添加 StartupService 跨平台开机自启动服务" ``` --- ## Task 4: ViewBViewModel 添加属性和逻辑 **Files:** - Modify: `src/YZWater.Core/ViewModels/ViewBViewModel.cs` - [ ] **Step 1: 添加 RunAtStartup 属性** 在 `_isConnecting` 字段(第 40 行)之后添加: ```csharp [ObservableProperty] private bool _runAtStartup; ``` - [ ] **Step 2: 添加翻译文本属性** 在 `_connectingText` 字段(第 62 行)之后添加: ```csharp [ObservableProperty] private string _runAtStartupText; [ObservableProperty] private string _systemSettingsText; ``` - [ ] **Step 3: 在 UpdateTexts() 方法中添加赋值** 在 `UpdateTexts()` 方法中 `ConnectingText = _lang.Get("Connecting");`(第 92 行)之后添加: ```csharp RunAtStartupText = _lang.Get("RunAtStartup"); SystemSettingsText = _lang.Get("SystemSettings"); ``` - [ ] **Step 4: 添加 OnRunAtStartupChanged 部分方法** 在 `UpdateTexts()` 方法之后(第 93 行后),添加: ```csharp partial void OnRunAtStartupChanged(bool value) { var success = StartupService.SetAutoStart(value); if (!success) { _runAtStartup = !value; OnPropertyChanged(nameof(RunAtStartup)); Log.Warning("开机自启动设置失败,已回滚 UI 状态"); } else { var config = ConfigService.GetConfig(); if (config != null) { config.RunAtStartup = value; ConfigService.SaveConfig(); Log.Information("开机自启动已{Status}", value ? "启用" : "禁用"); } } } ``` - [ ] **Step 5: 修改 LoadConfig() 加载 RunAtStartup** 在 `LoadConfig()` 方法中,`PumpFrequency = config.PumpFrequency;`(第 110 行)之后添加: ```csharp RunAtStartup = config.RunAtStartup; ``` - [ ] **Step 6: 修复 SaveConfig() bug** 将 `SaveConfig()` 方法(第 118-134 行)替换为: ```csharp /// /// 保存配置 /// [RelayCommand] private void SaveConfig() { var config = ConfigService.GetConfig(); if (config == null) { Log.Warning("配置为空,无法保存"); return; } config.PlcIp = PlcIp; config.PlcPort = PlcPort; config.AutoConnect = AutoConnect; config.LevelHighAlarm = LevelHighAlarm; config.LevelLowAlarm = LevelLowAlarm; config.FlowHighAlarm = FlowHighAlarm; config.PumpFrequency = PumpFrequency; // RunAtStartup 在 OnRunAtStartupChanged 中已单独保存 ConfigService.SaveConfig(); Log.Information("配置已保存"); } ``` - [ ] **Step 7: 构建验证** Run: `dotnet build src/YZWater.Core` Expected: 编译成功 - [ ] **Step 8: 提交** ```bash git add src/YZWater.Core/ViewModels/ViewBViewModel.cs git commit -m "feat: ViewBViewModel 添加开机自启动属性和逻辑" ``` --- ## Task 5: LanguageService 添加 i18n 键 **Files:** - Modify: `src/YZWater.Core/Services/LanguageService.cs` - [ ] **Step 1: 在中文词典中添加键** 在 `["Connecting"] = "连接中...",`(第 145 行)之后添加: ```csharp ["RunAtStartup"] = "开机自启动", ["SystemSettings"] = "系统设置", ``` - [ ] **Step 2: 在英文词典中添加键** 在 `["Connecting"] = "Connecting...";`(约第 392 行)之后添加: ```csharp ["RunAtStartup"] = "START WITH WINDOWS", ["SystemSettings"] = "SYSTEM SETTINGS", ``` - [ ] **Step 3: 构建验证** Run: `dotnet build src/YZWater.Core` Expected: 编译成功 - [ ] **Step 4: 提交** ```bash git add src/YZWater.Core/Services/LanguageService.cs git commit -m "feat: 添加开机自启动中英文翻译" ``` --- ## Task 6: ViewBView 添加 UI **Files:** - Modify: `src/YZWater.Avalonia/Views/ViewBView.axaml` - [ ] **Step 1: 调整 Grid RowDefinitions** 将第 24 行的 `RowDefinitions="Auto,Auto"` 改为 `RowDefinitions="Auto,Auto,Auto"`,为新区块增加一行。 - [ ] **Step 2: 将"泵频率设置"移到 Row 1** 将"泵频率设置"的 `Border`(第 80-95 行)中的 `Grid.Row="1"` 改为 `Grid.Row="1"`(保持不变),但将 `Margin` 从 `"0,4,4,0"` 改为 `"0,4,4,4"`。 - [ ] **Step 3: 将"操作按钮"移到 Row 2** 将"操作按钮"的 `Border`(第 97-108 行)中的 `Grid.Row="1"` 改为 `Grid.Row="2"`,`Margin` 从 `"4,4,0,0"` 改为 `"4,4,0,4"`。 - [ ] **Step 4: 在 Row 1 Column 1 添加系统设置区块** 在"泵频率设置" `Border`(第 95 行 ``)之后,"操作按钮" `Border` 之前,插入: ```xml ``` - [ ] **Step 5: 构建验证** Run: `dotnet build src/YZWater.Avalonia` Expected: 编译成功 - [ ] **Step 6: 提交** ```bash git add src/YZWater.Avalonia/Views/ViewBView.axaml git commit -m "feat: ViewBView 添加开机自启动 ToggleSwitch" ``` --- ## Task 7: 集成测试 - [ ] **Step 1: 全量构建** Run: `dotnet build` Expected: 编译成功,无错误 - [ ] **Step 2: 运行应用验证** Run: `dotnet run --project src/YZWater.Avalonia` 手动验证: 1. 登录应用 2. 导航到参数设置页面(ViewB) 3. 确认"系统设置"区块可见,包含"开机自启动"标签和 ToggleSwitch 4. 切换 ToggleSwitch 为开启状态 5. (Windows)打开注册表编辑器,检查 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` 下是否有 `YZWater` 键 6. 关闭应用,重新打开,确认 ToggleSwitch 状态已恢复 7. 切换 ToggleSwitch 为关闭状态,确认注册表键已删除 8. 切换语言为英文,确认"开机自启动"变为"START WITH WINDOWS" - [ ] **Step 3: 提交(如果有修改)** ```bash git add -A git commit -m "fix: 开机自启动集成测试修复" ``` --- ## 完成标准 - [ ] `SystemConfig` 包含 `RunAtStartup` 属性 - [ ] `StartupService` 支持 Windows/Linux/macOS 三平台 - [ ] ViewB 页面显示"系统设置"区块,包含开机自启动开关 - [ ] 开关切换即时生效(写入注册表/文件 + 持久化到 JSON) - [ ] 重启应用后状态正确恢复 - [ ] 中英文翻译完整 - [ ] `SaveConfig()` bug 已修复 - [ ] 全量构建无错误