[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1r4wkmrhtqh40":3,"$f1temdeireyiaa":39,"$f2d22qi2btqsjh":43,"$fubxv6mx382y9":44},{"id":4,"title":5,"slug":6,"excerpt":7,"cover":8,"status":9,"publishedAt":10,"viewCount":11,"likeCount":12,"favoriteCount":12,"createdAt":13,"updatedAt":14,"category":15,"author":18,"tags":21,"_count":36,"content":37,"attachments":38},10,"Hello KSP Launcher v1.4.0","hello-ksp-launcher-v1-4-0","本版本为实例和存档新增了完整的飞船（`.craft`）管理器，支持拖放导入；使推荐\u002F建议对话框变为渐进式且按模块处理；通过懒加载加速实例详情页；并让模组列表按实例持久化其视图状态。","","published","2026-09-19T04:12:05.722Z",0,1,"2026-09-19T04:11:05.513Z","2026-09-21T13:35:11.885Z",{"id":16,"name":17,"slug":17},7,"发布日志",{"id":12,"username":19,"nickname":19,"avatar":20},"zhuwenqian",null,[22,26,30,33],{"id":23,"name":24,"slug":25},3,"GUI","gui",{"id":27,"name":28,"slug":29},4,"Qt6","qt6",{"id":31,"name":32,"slug":32},6,"C++",{"id":34,"name":35,"slug":35},13,"KSP",{"comments":11},"# 发布说明\n\n## v1.4.0 — 飞船管理、渐进式模组选择与 UI 性能（2026-09-18）\n\n本版本为实例和存档新增了完整的飞船（`.craft`）管理器，支持拖放导入；使推荐\u002F建议对话框变为渐进式且按模块处理；通过懒加载加速实例详情页；并让模组列表按实例持久化其视图状态。\n\n### 飞船管理（实例与存档）\n\n新的飞船管理器可为任意实例和任意存档列出、查看并导入 `.craft` 文件，布局仿照存档管理器的标签页放置方式。\n\n- **实例飞船管理器**（`shiptabpage.{h,cpp}`，新增）—— 一个 `QTabWidget`，含 **VAB \u002F SPH** 标签页，列出 `.craft` 文件（自动排除 `.loadmeta`、`.craft.original` 等）；每行显示飞船名称（去掉 `.craft`）和游戏版本（从 `version = X.Y.Z` 解析，缺失时显示“未知”）。\n- **飞船详情（列表内第二页）**—— 点击一行会打开名称（`ship = XXX`）、游戏版本和只读描述（`description = XXX`），右侧显示来自 `Ships\u002F@thumbs\u002FVAB|SPH` 的匹配缩略图（缺失时使用火箭 SVG 回退图）。\n- **解析**（`instancemanager_ships.cpp`）—— `listCraftFiles`（按文件名排序，仅 `.craft`）、`loadCraftInfo`（只读 `ship\u002Fversion\u002Fdescription`，名称回退到文件名）、`getShipThumbPath`（png\u002Fjpg）、`moveCraftToTrash`。\n- **入口点**—— 实例详情侧边栏新增“飞船管理”第 5 个标签页（`showSection(5)`，`rocket.svg`）；存档详情页新增“飞船管理”第 3 个标签页，通过可设置的基路径（`setShipsBase`）复用同一个 `ShipTabPage`，该路径会自动定位到 `instanceRoot\u002Fsaves\u002FsaveName\u002FShips\u002FVAB|SPH`。\n- **导入飞船**—— 列表工具栏新增“导入飞船”按钮（`download.svg`）；可通过 `QFileDialog`（多选）导入，或直接将 `.craft` 拖放到 VAB\u002FSPH 标签页；导入到当前标签页的文件夹；名称冲突不区分大小写，会弹出“覆盖飞船”确认（是 = 覆盖 \u002F 否 = 跳过 \u002F 取消 = 中止剩余操作），并保留目标原有大小写。\n- **删除到回收站**—— 每行现在都有垃圾桶按钮（存档列表中旧的装饰性箭头也一并替换）；删除操作在 Windows 上将 `.craft` \u002F 存档文件夹移至系统回收站（`SHFileOperation(FO_DELETE + FOF_ALLOWUNDO)`），其他平台则永久删除。\n- **翻译\u002F测试**—— 新的 ShipTabPage 字符串已全部翻译为 en_US（0 个未完成）；`test_launcher` 新增 `TestShips`（顶层键解析、嵌套键不会覆盖、名称回退、列表过滤、缩略图定位）。导入\u002F回收站流程没有自动化测试（它们依赖模态对话框和实例路径，符合项目惯例）。\n\n### 渐进式、按模块的推荐\u002F建议对话框\n\n现在安装模块时，会逐个模块解析并询问其推荐\u002F建议，而不是将所有内容聚合成一个巨大列表。\n\n- **收集**（`relationshipresolver.{h,cpp}`、`ckan.{h,cpp}`）—— 新增 `collectOptionalFor(parent, curInstallSet, wantRecommends)`，仅收集**单个父模块自身**的 Recommends\u002FSuggests 候选项，跳过已安装\u002F已选择\u002F冲突项，且**不自动级联**。旧的全量聚合不再用于弹窗。\n- **安装流程**（`installservice.cpp` `resolveInstallSet`）—— 两阶段渐进式解析：\n  - **阶段 1（推荐）**—— 对每个显式安装的模块，一次显示一个推荐对话框；将选择合并到安装集，并将新添加的模块加入队列（FIFO），直到没有新模块出现。\n  - **阶段 2（建议）**—— 对每个显式模块显示一个建议对话框；如果新选择的模块的推荐未在阶段 1 显示，则先询问这些推荐，再询问其建议。\n  - 每个模块只安装一次（按标识符去重）；没有候选项的模块静默跳过；每个对话框标题标明其来源父模块。\n- **选择对话框**（`moddecision.{h,cpp}`）—— `askOptionalModules` 新增了一个 Trak-a-dock 风格的“全选 \u002F 全不选”切换按钮，会根据当前状态翻转，并新增 `parentName` 上下文标题。**取消按钮已移除**——唯一退出方式是“安装所选”；放弃安装会返回模组管理。\n- **测试**—— `test_libckan` 新增 `collectOptionalNoCascade`（按模块、不级联、无交叉污染）和 `collectOptionalSkipsInstalledAndSelected`。全部通过。\n\n### 模组管理 UI（分隔条与按实例状态）\n\n- **可拖动的列表\u002F详情分隔条**（`modstabpage.cpp`、`configmanager.{h,cpp}`）—— 一个垂直 `QSplitter`（`modSplitter`）位于模组表格和 4 个详情标签页（元数据\u002F文件\u002F关系\u002F版本）之间；拖动可调整列表高度（列表保持在上方，详情标签页下移）。初始为 3:2 分割，两个窗格均不可折叠；底部操作栏保持原位。顶部窗格高度持久化到 `HKSPL.json`（`modSplitterTopHeight`，全局），以 250ms 防抖保存；在首次 `showEvent` 中按分隔条的实际高度精确应用。\n- **按实例列表状态**（`configmanager.{h,cpp}`、`modstabpage.cpp`）—— 每个实例的模组列表在实例切换\u002F重启后恢复精确视图：搜索文本、状态过滤（全部\u002F已安装\u002F可升级\u002F未安装）、标签过滤、详情标签页索引、排序列+顺序、垂直滚动位置和所选模块。变更时保存（300ms 防抖；切换实例前刷新）到专用 `modListViewState` 区段，以实例 id 为键。在异步列表加载后恢复（`restoreListStateAfterLoad`），并用 `m_restorePending` 标志区分首次进入加载与同实例刷新。\n\n### 实例详情页——懒加载\n\n进入实例管理不再预先加载所有内容；每个次级标签页仅在进入时获取其数据。\n\n- `instancedetailpage.cpp` 的 `showSection`——模组管理标签页触发 `prepareMods()`（异步索引 + DLL 扫描），存档标签页触发 `loadSaves()`，飞船标签页触发 `loadShips()`。页面打开在“游戏设置”上，前期不执行繁重工作。\n- **模组标签页**（`modstabpage.{h,cpp}`）—— `setInstance` 仅记录实例，`setTabActive(true)` 在每次进入时重新准备模组。\n- **存档标签页**（`savestabpage.{h,cpp}`）—— `loadSaves()` 在 `QtConcurrent::run` 后台线程中运行，遍历目录并逐文件解析 `persistent.sfs`，通过 `QFutureWatcher` 填充列表（带有“正在加载存档...”占位符）；删除后刷新复用同一异步路径。\n- **飞船标签页**（`shiptabpage.{h,cpp}`）—— `loadShips()` 在后台线程中解析 VAB + SPH，并通过 watcher 填充；行构建被拆分为 `addShipRow`。\n- **线程安全**—— `InstanceManager::listSaves\u002FloadSaveInfo\u002FlistCraftFiles\u002FloadCraftInfo` 是纯文件读取，没有共享可变状态，可安全地在主线程之外调用。\n\n### 崩溃日志对话框——上下文与一键日志打包\n\n- **上下文窗口**（`playerloganalyzer.{h,cpp}`）—— `analyzePlayerLog` 新增 `context` 字段；`extractPlayerLogContext` 取关键错误行（崩溃标记 `Caught fatal signal` \u002F OOM），并从该处向上回溯最多 40 行（`kContextLeadingLines`），再取到日志末尾。\n- **对话框**（`mainwindow.cpp` 的 `maybeShowCrashAnalysis`）—— 一个只读、可选择的 `QPlainTextEdit`（最小高度 240px）现在会在消息框中粘贴错误上下文，并新增“保存日志”按钮。\n- **一键打包**（`mainwindow.{h,cpp}` 的 `packLogToZip`）—— 将整个 `Player.log` 流式写入 zip（使用 miniz 回调，绝不把数百 MB 的日志加载到内存），保存到启动器旁边，命名为 `\u003Ccleaned-instance>-\u003Cid8>_\u003CyyMMdd_HHmmss>.zip`；非法文件名字符替换为下划线。成功时显示完整路径；失败时发出警告，且不会留下不完整的 zip。\n- **测试**—— `test_launcher` 新增三个 `extractPlayerLogContext` 用例（≥40 行回溯起点、短日志头部起点、无崩溃返回空）。\n\n### UI 润色\n\n- **次级侧边栏对齐**—— 实例详情和存档详情的次级菜单现在作为左列贯穿整个窗口高度，宽度统一为 **220px**，与主页侧边栏一致（返回\u002F标题栏移入内容区域顶部）。\n\n### 技术栈\n\n- **框架**：Qt 6（Widgets、Svg、Network、Concurrent）\n- **语言**：C++17\n- **构建**：CMake ≥ 3.16；Qt 6.11\n- **测试**：`test_libckan` + `test_launcher`（全部通过）",[],{"postCount":4,"commentCount":12,"tagCount":40,"categoryCount":41,"totalViews":42},11,5,54,[],{"prev":45,"next":49},{"id":46,"title":47,"slug":48},9,"libckan API 文档","libckan-api-文档",{"id":40,"title":50,"slug":51},"HKSPL Release 1.4.1","hkspl-release-1-4-1"]