feat.文档更新

This commit is contained in:
lincube
2026-06-08 03:54:33 +08:00
parent 7db72fbcd0
commit 49af6601aa
247 changed files with 2939 additions and 0 deletions

178
docs/archive/AOT_PUBLISH.md Normal file
View File

@@ -0,0 +1,178 @@
# Launcher AOT 单文件发布指南
## 什么是 AOT
AOTAhead-of-Time编译将 .NET 代码在构建时直接编译为本地机器码,而不是在运行时通过 JIT 编译。
### AOT 的优势
| 特性 | JIT 模式 | AOT 模式 |
|------|---------|---------|
| 启动速度 | 慢(需要编译) | 快(直接执行) |
| 依赖文件 | 多(.dll, runtimeconfig.json | 少(单文件) |
| 需要 .NET Runtime | 是 | 否 |
| 文件体积 | 小 | 稍大(但单文件更方便) |
| 反编译难度 | 容易 | 困难 |
## 发布方式
### 方式一:使用 PowerShell 脚本(推荐)
```powershell
# 默认发布win-x64单文件自包含
.\scripts\Publish-AOT.ps1
# 指定运行时
.\scripts\Publish-AOT.ps1 -RuntimeIdentifier win-x64
# 不压缩(体积更大但启动更快)
.\scripts\Publish-AOT.ps1 -Compress:$false
```
### 方式二:使用 dotnet CLI
```bash
# 基本 AOT 发布
dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
-c Release `
-r win-x64 `
--self-contained `
-p:PublishAot=true `
-p:PublishSingleFile=true `
-p:EnableCompressionInSingleFile=true
# 输出目录
# bin/Release/net10.0/win-x64/publish/
```
### 方式三:使用 MSBuild
```bash
msbuild LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
/t:Publish `
/p:Configuration=Release `
/p:RuntimeIdentifier=win-x64 `
/p:PublishAot=true `
/p:PublishSingleFile=true
```
## 支持的运行时
| 运行时标识符 | 说明 |
|-------------|------|
| `win-x64` | Windows 64位推荐 |
| `win-x86` | Windows 32位 |
| `win-arm64` | Windows ARM64 |
| `linux-x64` | Linux 64位 |
| `linux-arm64` | Linux ARM64 |
| `osx-x64` | macOS 64位 |
| `osx-arm64` | macOS ARM64 (Apple Silicon) |
## 文件体积对比
### 普通发布(非 AOT
```
LanMountainDesktop.Launcher.exe 150 KB
LanMountainDesktop.Launcher.dll 200 KB
Avalonia.dll 1.2 MB
...(数十个依赖文件)
总计: ~15 MB
```
### AOT 单文件发布
```
LanMountainDesktop.Launcher.exe 8-12 MB单文件包含所有依赖
```
## 注意事项
### 1. 修剪Trimming
AOT 会自动移除未使用的代码以减小体积。某些反射代码可能需要特殊处理:
```csharp
// 如果类型被反射使用,需要保留
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class MyClass { }
```
### 2. Avalonia 兼容性
- ✅ Avalonia 11.x 完全支持 AOT
- ✅ 使用 Compiled Bindings已在项目中启用
- ✅ 避免动态 XAML 加载
### 3. Json 序列化
使用 `JsonSerializer` 时需要源生成器:
```csharp
[JsonSerializable(typeof(MyType))]
internal partial class MyJsonContext : JsonSerializerContext { }
```
### 4. 单文件特殊处理
某些文件需要嵌入到单文件中:
```xml
<ItemGroup>
<EmbeddedResource Include="Assets\logo.ico" />
</ItemGroup>
```
## 故障排除
### 发布失败
1. **检查 .NET SDK 版本**
```bash
dotnet --version # 需要 10.0 或更高
```
2. **安装 AOT 工作负载**
```bash
dotnet workload install wasm-tools # 如果需要 WebAssembly AOT
```
3. **Visual Studio 要求**
- 需要 VS 2022 17.8+ 或 VS Code + C# Dev Kit
### 运行时错误
1. **缺少类型**
- 在 `.csproj` 中添加 `<TrimmerRootAssembly>`
2. **反射失败**
- 使用 `[DynamicallyAccessedMembers]` 标记
3. **DllNotFoundException**
- 确保所有 native 库都包含在发布中
## 性能对比
| 指标 | JIT | AOT | 提升 |
|------|-----|-----|------|
| 启动时间 | 2-3 秒 | 0.5-1 秒 | 2-3x |
| 内存占用 | 较高 | 较低 | 20-30% |
| 首次响应 | 慢 | 快 | 显著 |
## 推荐配置
对于 Launcher 项目,推荐使用以下配置:
```xml
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
```
这样发布的结果:
- ✅ 单文件可执行
- ✅ 无需 .NET Runtime
- ✅ 启动速度快
- ✅ 文件体积合理8-12 MB

View File

@@ -0,0 +1,269 @@
# 架构文档 / Architecture
## 中文
### 仓库结构
| 路径 | 角色 |
| --- | --- |
| `LanMountainDesktop/` | 主桌面宿主应用,包含 UI、服务、组件系统、插件运行时接入 |
| **`LanMountainDesktop.Launcher/`** | **启动器 - 负责 OOBE、Splash、版本目录选择与主程序启动** |
| **`LanMountainDesktop.AirAppRuntime/`** | **Air APP 独立运行容器 - 负责 Air APP IPC、实例表与 AirAppHost 进程生命周期** |
| `LanMountainDesktop.PluginSdk/` | 官方插件 SDK定义插件可依赖的公开接口与打包行为 |
| `LanMountainDesktop.Shared.Contracts/` | 宿主与插件共享的稳定契约类型 |
| `LanMountainDesktop.Appearance/` | 主题、圆角、外观资源相关基础设施 |
| `LanMountainDesktop.Settings.Core/` | 设置域、持久化和设置基础抽象 |
| `LanMountainDesktop.DesktopHost/` | 桌面宿主流程与生命周期相关逻辑 |
| `LanMountainDesktop.DesktopComponents.Runtime/` | 组件运行时支撑能力 |
| `LanMountainDesktop.Host.Abstractions/` | 宿主侧抽象接口 |
| `LanMountainDesktop.PluginTemplate/` | `dotnet new lmd-plugin` 官方模板 |
| `LanMountainDesktop.Tests/` | 宿主与 SDK 的测试项目 |
### 宿主启动主线
**生产环境启动流程 (通过 Launcher):**
1. 用户启动 `LanMountainDesktop.Launcher.exe`
2. Launcher 扫描 `app-*` 目录,选择最佳版本 (优先 `.current` 标记,然后按版本号降序)
3. 首次启动显示 OOBE 引导 (`OobeWindow`)
4. 显示 Splash 启动动画 (`SplashWindow`)
5. 预启动包根下的 `LanMountainDesktop.AirAppRuntime`(框架依赖 JIT 进程)
6. 启动主程序 `app-{version}/LanMountainDesktop.exe`(更新检查、下载、应用、回滚和插件 pending 队列均由 Host 处理)
7. 主程序启动成功后将 Host PID 附加给 AirApp Runtime并清理标记为 `.destroy` 的旧版本
**主程序启动流程 (LanMountainDesktop.exe):**
启动入口在 `LanMountainDesktop/Program.cs`
1. 初始化日志、启动诊断和 Host 桌面生命周期
2. 初始化遥测身份、崩溃遥测与使用遥测
3. 构建 Avalonia `AppBuilder`
4. 进入 `LanMountainDesktop/App.axaml.cs`
5. 初始化主题、语言、设置窗口服务、天气定位刷新
6. 初始化桌面壳层、主窗口、托盘、插件运行时
### 运行时主数据流
- 设置流:`Settings.Core` 提供基础设置能力,宿主通过 facade 读取和监听设置变化
- 外观流:`Appearance` 提供主题和圆角资源,宿主在 `App.axaml.cs` 中应用到资源字典
- 组件流:`LanMountainDesktop/ComponentSystem/` 维护内置组件定义、注册和扩展接入
- 插件流:宿主侧 `plugins/` 负责 `.laapp` 的发现、安装、替换、激活与共享契约装配
- 设置页流:插件运行时可把自己的设置页注册进宿主设置窗口
### 关键目录落点
`LanMountainDesktop/` 内高频目录:
- `Views/`:窗口、页面、组件视图
- `ViewModels/`:视图模型
- `Services/`:业务服务、持久化、启动、遥测等
- `ComponentSystem/`:组件定义、注册、扩展加载
- `plugins/`:宿主侧插件运行时
- `Theme/``Styles/`:主题资源、样式、外观应用
- `DesktopEditing/`:桌面布局编辑相关逻辑
- `Localization/`:本地化资源
### 插件边界
- 插件 SDK 权威定义在 `LanMountainDesktop.PluginSdk/`
- 宿主与插件共享的稳定通信类型在 `LanMountainDesktop.Shared.Contracts/`
- 插件市场和开发者生态资料不在本仓库维护
- 本地 market 调试从兄弟仓库 `..\\LanAirApp` 读取数据
### 测试边界
`LanMountainDesktop.Tests/` 当前主要覆盖:
- 圆角与外观相关基础
- 组件放置与编辑数据
- 组件设置服务
- UI 异常防护
- 白板笔记持久化
涉及宿主行为、SDK 契约、布局计算或设置持久化的改动,应优先补对应测试。
### Launcher 架构详解
#### 职责范围
`LanMountainDesktop.Launcher/` 作为应用的唯一入口,负责:
1. **OOBE (首次体验)** - 首次启动引导和欢迎页面
2. **Splash Screen** - 启动动画和加载进度显示
3. **版本管理** - 多版本并存、版本选择、版本回退
4. **无更新职责** - 不检查、不下载、不应用、不回滚更新;更新系统完全由 Host 接管
5. **插件维护命令** - 保留 `plugin install` / `plugin update` 作为兼容 CLI应用内插件市场由 Host 处理
#### 核心服务
| 服务 | 职责 |
|------|------|
| `DeploymentLocator` | 扫描和定位 `app-*` 版本目录,选择最佳版本 |
| `LauncherOrchestrator` / `LaunchPipeline` | 协调 OOBE → Splash → AirApp Runtime 预启动 → 启动主程序 |
| `OobeStateService` | 管理首次运行状态 |
| `PluginInstallerService` | CLI 维护:`plugin install` 直接安装 `.laapp` |
| `PluginUpgradeQueueService` | CLI 维护:`plugin update` 应用待处理队列(正常市场安装/升级由 Host 处理) |
#### 版本管理机制
**目录结构:**
```
安装根目录/
├── LanMountainDesktop.Launcher.exe ← 唯一入口
├── app-1.0.0/ ← 版本目录
│ ├── .current ← 当前版本标记
│ ├── LanMountainDesktop.exe
│ └── ...
├── app-1.0.1/ ← 新版本
│ ├── .partial ← 下载中标记
│ └── ...
└── .Launcher/ ← Launcher 数据
├── state/ ← OOBE 状态
├── update/incoming/ ← 更新缓存
└── snapshots/ ← 更新快照
```
**版本选择算法:**
1. 扫描所有 `app-*` 目录
2. 过滤掉带 `.destroy``.partial` 标记的目录
3. 优先选择带 `.current` 标记的版本
4. 如果没有 `.current`,选择版本号最高的
**版本标记文件:**
- `.current` - 标记当前使用的版本
- `.partial` - 标记下载未完成的版本 (更新失败时自动清理)
- `.destroy` - 标记待删除的旧版本 (下次启动时清理)
#### 更新流程
**增量更新:**
1. Host 的 `UpdateOrchestrator` 检查更新、解析 manifest并下载 PLONDS file map、签名和对象文件到 `.Launcher/update/incoming/`
2. Host 写入 `deployment.lock`,随后在 Host 进程内进入 `UpdateInstallGateway`
3. Host 负责签名校验、创建目标 `app-{new}/`、应用文件、验证 hash、切换 `.current`、写入快照和清理 incoming
4. 失败时 Host 使用快照尝试回滚;手动回滚通过 Host 设置页进入 `UpdateRollbackGateway`
**原子化保证:**
- 更新过程中保持 `.partial` 标记
- 任何失败都会触发回滚
- 旧版本保留直到新版本验证通过
- 快照记录允许手动回退
**版本回退:**
```bash
Host 设置页 → 更新 → 回滚
```
回退会:
1. 读取最新的更新快照
2. 移除当前版本的 `.current` 标记
3. 添加 `.current` 到上一个版本
4. 标记当前版本为 `.destroy`
#### CI/CD 集成
**发布产物结构:**
```
GitHub Release Assets:
├── LanMountainDesktop-Setup-1.0.1-x64.exe (安装包)
├── app-1.0.1.zip (完整应用包)
├── delta-1.0.0-to-1.0.1.zip (增量包)
├── files-1.0.1.json (文件清单)
└── files-1.0.1.json.sig (RSA 签名)
```
**增量包生成:**
- `scripts/Generate-DeltaPackage.ps1` - 对比两个版本生成增量包
- `scripts/Sign-FileMap.ps1` - 对 `files.json` 进行 RSA 签名
- `.github/workflows/release.yml` - 自动生成并上传增量包
**安装器集成:**
- Inno Setup 脚本修改为安装 Launcher 到根目录
- 主程序安装到 `app-{version}/` 子目录
- 快捷方式指向 `LanMountainDesktop.Launcher.exe`
- 安装后验证 Launcher 和 app 目录存在
## English
This repository is organized around a desktop host app plus a host-side plugin ecosystem. `LanMountainDesktop/` contains the application entry points, UI, services, component system, and plugin runtime integration. The surrounding projects provide the public SDK, shared contracts, appearance infrastructure, settings primitives, host abstractions, runtime support, and tests.
**Launcher Architecture**: `LanMountainDesktop.Launcher/` serves as the single entry point, managing OOBE, splash screen, version selection, and host startup. Update check/download/apply/rollback orchestration is fully Host-owned; the Launcher does not expose update CLI commands. In-app plugin market installation is also Host-owned. The Launcher still keeps plugin CLI commands as maintenance compatibility entry points. It uses a version directory structure (`app-{version}/`) with marker files (`.current`, `.partial`, `.destroy`) only to select the host version to start. See the Chinese section above for detailed architecture documentation.
The runtime flow starts with the Launcher selecting the best version, then proceeds into `Program.cs`, into `App.axaml.cs`, initializes settings/theme/localization services, then boots the desktop shell, tray, windows, and plugin runtime. The most important behavior boundaries are component registration, plugin activation, appearance resources, and settings persistence.
## VeloPack Integration Note
- Incremental package build/publish has moved to VeloPack native assets (
eleases.win.json + *.nupkg).
- Launcher runtime responsibilities are OOBE, startup orchestration, AirApp Runtime pre-start, version directory selection, and Host launch. Update check/download/apply/rollback stays in the Host.
## Plugin Isolation Modes
The current plugin runtime is still in-process. `PluginRuntimeService` and `PluginLoader` load plugin code inside the Host process, while `PluginLoadContext` only provides assembly isolation, not process isolation.
The repository now reserves three runtime modes:
- `in-proc`: current default and compatibility mode
- `isolated-background`: phase-1 mode, where background logic moves into a dedicated worker process and Host UI becomes a thin IPC-driven shell
- `isolated-window`: phase-2 mode, where plugin UI renders out of process and Host embeds a platform window handle
Two new supporting packages define the isolation boundary:
- `LanMountainDesktop.PluginIsolation.Contracts/`: transport-neutral DTOs, route constants, error codes, capabilities, and JSON context
- `LanMountainDesktop.PluginIsolation.Ipc/`: ClassIsland-inspired IPC facade that centralizes startup constants, routed notify IDs, and client/server wrappers over the future `dotnetCampus.Ipc` transport binding
For the detailed design, migration path, UI strategy, and residual risks, see `docs/PLUGIN_PROCESS_ISOLATION_ARCHITECTURE.md`.
## External IPC Public API
- The current IPC mainline is external integration, not plugin process isolation.
- `LanMountainDesktop.Shared.IPC` is the unified IPC base for Host public services, Launcher/OOBE startup notifications, and plugin-contributed public services.
- Strongly typed command/query access uses `[IpcPublic]` contracts plus `dotnetCampus.Ipc` generated proxy/joint support.
- One-way events use `JsonIpcDirectRoutedProvider.NotifyAsync` with fixed top-level notify IDs.
- Host remains the single external IPC entry point even when a capability is contributed by a plugin.
See `docs/EXTERNAL_IPC_ARCHITECTURE.md` for the detailed contract and migration model.
## Air APP Lifecycle
- `LanMountainDesktop.AirAppRuntime` is the lifecycle bridge between the desktop host and Air APP processes.
- The desktop host requests built-in Air APP operations through `IAirAppLifecycleService` on `LanMountainDesktop.AirAppRuntime.v1`.
- Launcher pre-starts `LanMountainDesktop.AirAppRuntime` during normal startup and attaches the launched Host PID through `IAirAppRuntimeControlService`.
- If that pipe is not available because the desktop host was started directly from IDE/dev tooling, the host starts `LanMountainDesktop.AirAppRuntime` and retries the request.
- AirApp Runtime owns Air APP process creation, activation, instance-key de-duplication, registration tracking, and exited-process cleanup.
- `LanMountainDesktop.AirAppHost` stays an independent rendering process and registers/unregisters itself with AirApp Runtime.
- Launcher waits for the desktop host startup path only; AirApp Runtime remains alive while Launcher/Host/requester or any AirAppHost process is alive, and exits when idle.
- Air APP windows are ordinary application windows: they do not use fused desktop bottom-most services and do not use global `Topmost` promotion.
## Fused Desktop Window Layer
- `TransparentOverlayWindow` and `DesktopWidgetWindow` are desktop-surface windows.
- On Windows, desktop-surface windows may attach to the desktop icon host through `IWindowBottomMostService`, or fall back to `HWND_BOTTOM`.
- Fused desktop windows refresh their bottom-most layer after being opened, shown, or reloaded so they do not cover ordinary apps.
## Main Window Desktop Layer
- The main desktop host window has a separate developer option, `EnableMainWindowDesktopLayer`.
- This mode is mutually exclusive with fused desktop because fused desktop manages component windows while main-window desktop layer manages the host window itself.
- The main-window service is `IMainWindowDesktopLayerService`; it attaches only the main window to the desktop icon host on Windows and falls back to `HWND_BOTTOM`.
- The main-window service does not use fused desktop click-through region logic, so the main desktop window remains interactive.
- Main-window restore paths refresh the desktop-layer attachment instead of using temporary `Topmost` foreground promotion while this mode is enabled.
- Air APP windows remain ordinary application windows and are not handled by either desktop-layer service.
## Air APP Window Chrome
- `LanMountainDesktop.AirAppHost` owns Air APP window chrome through `AirAppWindowDescriptor`.
- Supported chrome modes are `Standard`, `Borderless`, `FullScreen`, `Tool`, and reserved `BackgroundOnly`.
- Built-in `world-clock` uses `Standard` chrome with FluentAvalonia `FAAppWindow` title-bar controls.
- Built-in `whiteboard` uses `FullScreen` chrome and supplies its own in-app exit affordance.
## Launcher OOBE / Elevation Contract
- Launcher OOBE state is owned by a per-user JSON file under `%LOCALAPPDATA%\LanMountainDesktop\.launcher\state\oobe-state.json`.
- Same-user reinstall or upgrade should keep OOBE completed.
- `first_run_completed` is legacy migration-only data.
- The recognized launch sources are `normal`, `postinstall`, `plugin-install`, and `debug-preview`.
- Auto-OOBE is only allowed for normal user-mode startup.
- `postinstall` may show OOBE only when the launcher is not elevated.
- `plugin-install` and `debug-preview` must not auto-open OOBE.
- Elevation is allowed only for the installer, full installer update application, and user-confirmed legacy uninstall.
- Default plugin install targets the Host data root (`AppDataPathProvider.GetDataRoot()/Extensions/Plugins`) and should not ask for UAC when that directory is writable.
- In portable data mode, plugin packages follow the configured application data root. If that root is under an administrator-protected install path, Host downloads/verifies the package from a user-writable staging directory and invokes the restricted Launcher `plugin install` command with UAC to copy only into the configured data root.
- Marketplace plugin installs are queued under the Host data root when writable and take effect after restart; protected portable installs are applied immediately through the elevated maintenance command and still require restart before loading.

View File

@@ -0,0 +1,335 @@
# 构建和部署指南
> LanMountainDesktop 完整构建、打包和发布流程
## 目录
- [本地构建](#本地构建)
- [发布构建](#发布构建)
- [生成安装包](#生成安装包)
- [CI/CD 流程](#cicd-流程)
- [手动发布](#手动发布)
## 本地构建
### 环境要求
- .NET SDK 10.0 或更高版本
- Windows 10/11 (推荐)
- Inno Setup 6 (仅生成安装包时需要)
### 快速构建
```bash
# 1. 还原依赖
dotnet restore LanMountainDesktop.slnx
# 2. 构建 Debug 版本
dotnet build LanMountainDesktop.slnx -c Debug
# 3. 运行主程序
dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj
```
### 构建 Release 版本
```bash
dotnet build LanMountainDesktop.slnx -c Release
```
## 发布构建
### Windows (x64, 自包含)
```bash
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj `
-c Release `
-o ./publish/windows-x64 `
--self-contained `
-r win-x64 `
-p:PublishSingleFile=false `
-p:DebugType=none `
-p:DebugSymbols=false
```
**发布后的目录结构:**
```
publish/windows-x64/
├── LanMountainDesktop.Launcher.exe ← 入口
├── app-{version}/ ← 主程序
│ ├── .current
│ ├── LanMountainDesktop.exe
│ └── ...
```
### Linux (x64)
```bash
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj `
-c Release `
-o ./publish/linux-x64 `
--self-contained `
-r linux-x64
```
### macOS (arm64)
```bash
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj `
-c Release `
-o ./publish/osx-arm64 `
--self-contained `
-r osx-arm64
```
## 生成安装包
### Windows 安装包 (Inno Setup)
**前提条件:**
```powershell
# 安装 Inno Setup
choco install innosetup -y
```
**生成安装包:**
```powershell
# 1. 发布应用
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj `
-c Release `
-o ./publish/windows-x64 `
--self-contained `
-r win-x64
# 2. 运行 Inno Setup 编译器
$version = "1.0.0"
$arch = "x64"
iscc.exe `
/DMyAppVersion=$version `
/DMyAppArch=$arch `
/DPublishDir="publish\windows-x64" `
/DMyOutputDir="build-installer" `
LanMountainDesktop\installer\LanMountainDesktop.iss
```
**输出:**
```
build-installer/
└── LanMountainDesktop-Setup-1.0.0-x64.exe
```
### Linux 包 (.deb)
```bash
# TODO: 添加 .deb 打包脚本
```
### macOS 包 (.dmg)
```bash
# TODO: 添加 .dmg 打包脚本
```
## CI/CD 流程
### GitHub Actions 工作流
项目使用 GitHub Actions 自动化构建和发布。
**触发条件:**
- 推送 `v*` 标签 (例如: `v1.0.0`)
- 手动触发 (workflow_dispatch)
**工作流文件:** `.github/workflows/release.yml`
### 发布流程
```
1. prepare job
├─ 解析版本号
└─ 设置构建变量
2. build-windows job
├─ 构建 x64 和 x86 版本
├─ 重组为 app-{version} 结构
├─ 生成增量包
├─ 生成 Inno Setup 安装包
└─ 上传 artifacts
3. build-linux job
├─ 构建 x64 版本
├─ 生成 .deb 包
└─ 上传 artifacts
4. build-macos job
├─ 构建 arm64 和 x64 版本
├─ 生成 .dmg 包
└─ 上传 artifacts
5. release job
├─ 下载所有 artifacts
├─ 创建 GitHub Release
└─ 上传所有安装包和增量包
```
### 发布产物
**GitHub Release Assets:**
```
LanMountainDesktop-v1.0.0/
├── LanMountainDesktop-Setup-1.0.0-x64.exe # Windows 安装包
├── LanMountainDesktop-Setup-1.0.0-x86.exe
├── LanMountainDesktop-1.0.0-linux-x64.deb # Linux 包
├── LanMountainDesktop-1.0.0-macos-arm64.dmg # macOS 包
├── app-1.0.0.zip # 完整应用包
├── delta-0.9.9-to-1.0.0.zip # 增量包
├── files-1.0.0.json # 文件清单
└── files-1.0.0.json.sig # RSA 签名
```
## 手动发布
### 1. 准备发布
```bash
# 1. 更新版本号
# 编辑 Directory.Build.props 中的 <Version>
# 2. 更新 CHANGELOG.md
# 记录本次发布的变更
# 3. 提交变更
git add .
git commit -m "chore: prepare release v1.0.0"
git push
```
### 2. 创建 Release 标签
```bash
# 创建标签
git tag v1.0.0
# 推送标签 (触发 CI)
git push origin v1.0.0
```
### 3. 等待 CI 完成
访问 GitHub Actions 页面,等待构建完成:
```
https://github.com/YourOrg/LanMountainDesktop/actions
```
### 4. 验证 Release
1. 访问 Releases 页面
2. 检查所有安装包是否上传成功
3. 下载并测试安装包
4. 验证增量更新功能
### 5. 发布公告
- 在 GitHub Release 中编辑发布说明
- 发布到社区/论坛
- 更新官网下载链接
## 增量包生成
### 手动生成增量包
```powershell
# 1. 准备两个版本的发布目录
dotnet publish ... -o ./publish/app-1.0.0
dotnet publish ... -o ./publish/app-1.0.1
# 2. 生成增量包
./scripts/Generate-DeltaPackage.ps1 `
-PreviousVersion "1.0.0" `
-CurrentVersion "1.0.1" `
-PreviousDir "./publish/app-1.0.0" `
-CurrentDir "./publish/app-1.0.1" `
-OutputDir "./delta-output"
# 3. 签名文件清单
./scripts/Sign-FileMap.ps1 `
-FilesJsonPath "./delta-output/files-1.0.1.json" `
-PrivateKeyPath "./private-key.pem"
```
**输出:**
```
delta-output/
├── delta-1.0.0-to-1.0.1.zip
├── files-1.0.1.json
└── files-1.0.1.json.sig
```
### 生成 RSA 密钥对
```powershell
# 生成私钥
openssl genrsa -out private-key.pem 2048
# 提取公钥
openssl rsa -in private-key.pem -pubout -out public-key.pem
```
**重要:**
- 私钥保存在安全位置 (GitHub Secrets)
- 公钥打包到 Launcher 中 (`.launcher/update/public-key.pem`)
## 版本号规范
遵循 [Semantic Versioning 2.0.0](https://semver.org/):
```
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
例如:
- 1.0.0 (正式版)
- 1.0.1 (补丁版本)
- 1.1.0 (新功能)
- 2.0.0 (破坏性变更)
- 1.0.0-beta.1 (预览版)
- 1.0.0-rc.1 (候选版本)
```
### 版本号更新规则
- **MAJOR**: 破坏性 API 变更
- **MINOR**: 新功能,向后兼容
- **PATCH**: Bug 修复,向后兼容
- **PRERELEASE**: 预览版标识 (alpha, beta, rc)
## 故障排除
### 构建失败
**问题**: `error NU1102: Unable to find package`
**解决**:
```bash
dotnet restore --force
dotnet nuget locals all --clear
```
### 发布失败
**问题**: Launcher 目录不存在
**解决**: 检查 `LanMountainDesktop.csproj` 中的 `CopyLauncherToPublish` 目标是否正确执行。
### 安装包生成失败
**问题**: Inno Setup 找不到文件
**解决**: 确保 `PublishDir` 路径正确,且包含 `app-{version}/` 目录结构。
## 相关文档
- [开发文档](DEVELOPMENT.md)
- [Launcher 架构](LAUNCHER.md)
- [更新系统](UPDATE_SYSTEM.md)
- [故障排除](TROUBLESHOOTING.md)

View File

@@ -0,0 +1,63 @@
# 协作文档 / Contributing
## 中文
### 适用范围
本文件适用于本仓库内的代码、文档、规格与测试协作。
### 基本流程
1. 先阅读 `README.md``docs/ARCHITECTURE.md``docs/DEVELOPMENT.md`
2. 如果是新功能、行为变更或跨模块调整,先检查是否需要补 `.trae/specs/`
3. 实现代码改动时,尽量同时补测试和必要文档
4. 提交 PR 前,至少确认构建、测试和相关文档链接可用
### 什么时候必须更新 spec
以下改动默认要补或更新 `.trae/specs/<feature>/`
- 新增用户可见功能
- 修改已有功能行为、交互或规则
- 调整设置页信息架构或主要视觉结构
- 修改插件宿主集成方式、共享契约或 SDK 使用模式
如果只是小范围重构、纯修复拼写、或不改变行为的内部清理,可以不新增 spec但仍要补必要测试。
### 什么时候必须更新文档
- 产品定位、版本阶段、生态边界变化:更新 `docs/PRODUCT.md`
- 仓库结构、模块职责、运行时边界变化:更新 `docs/ARCHITECTURE.md`
- 构建、运行、测试、打包步骤变化:更新 `docs/DEVELOPMENT.md`
- AI 协作入口、代码地图、执行约束变化:更新 `AGENTS.md``docs/ai/`
- 视觉或圆角规则变化:更新对应专题文档
### PR 预期
PR 说明至少要覆盖:
- 改了什么
- 为什么要改
- 如何验证
- 是否影响文档、spec 或迁移说明
如果改动涉及 UI、插件、设置页、打包或共享契约建议明确列出受影响区域。
### 测试预期
默认至少执行与改动相关的验证:
- `dotnet build LanMountainDesktop.slnx -c Debug`
- `dotnet test LanMountainDesktop.slnx -c Debug`
无法运行的检查要在 PR 里说明原因。
### 文档原则
- 每类事实只保留一个权威来源
- 根目录 `README.md` 面向人类入口,`AGENTS.md` 面向 AI 入口
- 不要在多个文件里复制同一段说明,只保留索引和跳转
## English
Keep the documentation model simple: `README.md` is the human entry point, `AGENTS.md` is the AI entry point, `docs/` stores durable project docs, and `.trae/specs/` stores feature-level specs. If a change affects behavior, boundaries, or workflows, update the corresponding source-of-truth document in the same PR.

View File

@@ -0,0 +1,99 @@
# 圆角设计规范 (LanMountain Desktop Corner Radius Spec)
## 核心理念 (Core Philosophy)
为了确保桌面组件在不同尺寸、缩放比例下都能保持视觉一致性和美感,阑山桌面采用了 **固定圆角风格预设 (Fixed Corner Radius Styles)**全面参考小米澎湃OS (Xiaomi HyperOS) 的设计语言。
此外,在系统管理与控制面板等特定区域,阑山桌面引入了 **Fluent** 预设,完全遵循 Microsoft Fluent Design System 规范,以便与宿主操作系统的应用视觉保持一致。
所有的组件和容器必须使用统一的资源键,禁止在 XAML 或代码中使用硬编码的像素值。
## 预设风格 (Preset Styles)
用户可以在设置中选择以下五种风格之一。系统会自动根据选中的风格动态映射全局圆角 Token。
| 风格 (ID) | 名称 (Local) | 组件圆角 (Component) | 设计语义 |
| :--- | :--- | :--- | :--- |
| **Sharp** | 锐利 | 20px | 紧凑、精确、利落 |
| **Balanced** | 平衡 | 24px | **默认值**。和谐、自然、普适 |
| **Rounded** | 圆润 | 28px | 保守、柔和、亲切 |
| **Open** | 开放 | 32px | 现代、沉浸、夸张 |
| **Fluent** | Fluent | 8px | Microsoft Fluent Design System。标准、规范、一致 |
## Token 阶梯映射 (Token Step Mapping)
每个风格都定义了一套完整的圆角阶梯,以确保在大容器包裹小元素时满足 **圆角嵌套一致性 (Nesting Consistency)**
| Token | Sharp | Balanced | Rounded | Open | Fluent | 典型场景 |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| **Micro** | 4px | 6px | 8px | 10px | 2px | 小图标容器、角标 (Badge) |
| **Xs** | 8px | 12px | 14px | 16px | 4px | 小标签 (Tag)、输入框 |
| **Sm** | 10px | 14px | 16px | 20px | 4px | 普通按钮、搜索栏、复选框 |
| **Md** | 14px | 20px | 24px | 28px | 8px | 悬浮菜单、小提示框、子卡片 |
| **Lg** | 20px | 28px | 32px | 36px | 8px | 普通面板、对话框内容区 |
| **Xl** | 24px | 32px | 36px | 40px | 12px | 大尺寸容器、设置中心页面 |
| **Island** | 28px | 36px | 40px | 44px | 16px | 任务栏、全局大悬浮容器 |
| **Component** | **20px** | **24px** | **28px** | **32px** | **8px** | **所有桌面组件 (Widget) 的主边框** |
## 系统设计特例约束 (System Design Exceptions)
> [!IMPORTANT]
> **局部作用域隔离原则 (Scope Isolation)**
> 为了确保系统级配置面板、向导及管理界面的设计规范性,部分特例区域必须**始终使用 Microsoft Fluent Design System 预设**,不受用户在“外观设置 -> 全局圆角”中所选风格的影响:
>
> 1. **设置窗口 (`SettingsWindow`)**:作为主配置中心,强制应用 Fluent 圆角,使其展现标准 Windows 应用的高级感与一致性。
> 2. **融合桌面组件库 (`FusedDesktopComponentLibraryWindow` / `FusedDesktopComponentLibraryControl`)**:小组件库的管理添加窗口本身属于系统级向导,强制采用 Fluent 圆角设计(如外壳圆角为 `DesignCornerRadiusLg`,内部按钮为 `DesignCornerRadiusSm`),保证交互的高级感与系统级管理界面对齐。
> 3. **系统弹出对话框 (`ContentDialog` / `FAContentDialog`)**:例如设置界面的重启确认、编辑桌面时的删除页面二级确认、电源菜单的二次确认等,通过全局 XAML 样式统一覆盖其所使用的 `OverlayCornerRadius` (8px)、`ControlCornerRadius` (4px) 以及相关的 `DesignCornerRadiusXxx` 令牌,以确保这些高优先级确认弹窗在任意窗口上层弹出时均保持 Fluent 风格。
> 4. **多开提示窗口 (`MultiInstancePromptWindow`)**:当多次启动软件时弹出的二级拦截警示窗口,属于独立启动器进程中的系统级安全提示,强制在 Window Resources 中硬编码重载为 Fluent 风格对应的圆角参数(如边角 8px交互按钮 4px
### 实现机制 (Implementation Mechanism)
在上述特例窗口的初始化过程中,通过在其根网格/容器元素(如 `RootGrid`)下调用 `ApplyFluentCornerRadius()`,在局部作用域内覆盖所有的 `DesignCornerRadiusXxx` 资源键为 Fluent 阶梯对应的值:
```csharp
private void ApplyFluentCornerRadius()
{
if (RootGrid is null) return;
var tokens = AppearanceCornerRadiusTokenFactory.Create(
GlobalAppearanceSettings.CornerRadiusStyleFluent);
RootGrid.Resources["DesignCornerRadiusMicro"] = tokens.Micro;
RootGrid.Resources["DesignCornerRadiusXs"] = tokens.Xs;
RootGrid.Resources["DesignCornerRadiusSm"] = tokens.Sm;
RootGrid.Resources["DesignCornerRadiusMd"] = tokens.Md;
RootGrid.Resources["DesignCornerRadiusLg"] = tokens.Lg;
RootGrid.Resources["DesignCornerRadiusXl"] = tokens.Xl;
RootGrid.Resources["DesignCornerRadiusIsland"] = tokens.Island;
RootGrid.Resources["DesignCornerRadiusComponent"] = tokens.Component;
}
```
这样使得所有内部子控件使用 `DynamicResource` 引用这些圆角资源时,解析到的都是隔离后且固定的 Fluent 设计弧度,实现不受全局用户偏好影响的精准渲染。
## 开发准则 (Implementation Rules)
> [!IMPORTANT]
> **1. 桌面组件强制约束**
> 所有桌面普通组件Widget / Desktop Component的根容器边框在设计时必须统一且仅使用 `{DynamicResource DesignCornerRadiusComponent}`。严禁对其进行任何比例运算或系数乘积(如 `* scale`),以确保用户的全局圆角缩放设置能被正确、成比例地应用。
> [!TIP]
> **2. 圆角嵌套规则**
> 当一个容器包裹另一个元素时,外层圆角应比内层圆角大一个阶梯。例如:
> - 外部大容器使用 `DesignCornerRadiusLg`
> - 内部小卡片使用 `DesignCornerRadiusMd`
> - 内部紧贴边缘的小图标或按钮使用 `DesignCornerRadiusSm`
> 这样可以保证两条圆弧的圆心趋于重合,视觉重心更稳固。
> [!CAUTION]
> **3. 禁止硬编码 (No Hardcoding)**
> 禁止写死数字(如 `CornerRadius="24"`)或私有资源。如果现有 Token 无法满足需求,应优先考虑使用 `SafeValue` 辅助方法封装,但必须声明理由。
## 常用资源键 (Common Resource Keys)
- `DesignCornerRadiusComponent` (桌面组件主框专用)
- `DesignCornerRadiusMicro`
- `DesignCornerRadiusSm`
- `DesignCornerRadiusMd`
- `DesignCornerRadiusLg`
- `DesignCornerRadiusXl`

172
docs/archive/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,172 @@
# 开发文档 / Development
## 中文
### 环境准备
- 安装 `.NET SDK 10`
- 桌面端建议优先在 Windows 上开发和验证
- 仓库主入口解决方案文件为 `LanMountainDesktop.slnx`
- SDK 版本由仓库根目录 `global.json` 锁定
### 常用命令
#### 还原与构建
```bash
dotnet restore
dotnet build LanMountainDesktop.slnx -c Debug
```
#### 运行桌面宿主
**开发模式 (直接运行主程序,跳过 Launcher):**
```bash
dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj
```
**生产模式 (通过 Launcher 启动):**
```bash
# 先构建 Launcher
dotnet build LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -c Debug
# 通过 Launcher 启动主程序
dotnet run --project LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -- launch
```
Air APP 开发调试时需要同时构建 `LanMountainDesktop.AirAppRuntime`。正常 Launcher 启动会预启动该 Runtime直接运行 Host 时Host 会在第一次打开 Air APP 时兜底启动 Runtime。
**Launcher 其他命令:**
```bash
# 安装插件
dotnet run --project LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -- plugin install <path-to-plugin.laapp>
# Launcher 不提供更新/回滚 CLI调试更新请运行主程序并使用 Host 更新服务。
```
#### 运行测试
```bash
dotnet test LanMountainDesktop.slnx -c Debug
```
### 常见工作区域
- 宿主应用:`LanMountainDesktop/`
- **Launcher (启动器)`LanMountainDesktop.Launcher/`**
- **AirApp Runtime (轻应用生命周期容器)`LanMountainDesktop.AirAppRuntime/`**
- Plugin SDK`LanMountainDesktop.PluginSdk/`
- 共享契约:`LanMountainDesktop.Shared.Contracts/`
- 测试:`LanMountainDesktop.Tests/`
- 插件打包脚本:`scripts/Pack-PluginPackages.ps1`
- **增量更新脚本:`scripts/Generate-DeltaPackage.ps1`, `scripts/Sign-FileMap.ps1`**
### 调试建议
- **Launcher 启动问题优先看 `LanMountainDesktop.Launcher/Program.cs``Shell/LauncherOrchestrator.cs``Startup/LaunchPipeline.cs`**
- **版本管理问题优先看 `LanMountainDesktop.Launcher/Deployment/DeploymentLocator.cs`**
- **更新检查、下载、应用和回滚问题优先看 `LanMountainDesktop/Services/Update/UpdateOrchestrator.cs``UpdateInstallGateway.cs``UpdateRollbackGateway.cs`**
- 启动问题优先看 `LanMountainDesktop/Program.cs``LanMountainDesktop/App.axaml.cs`
- 设置窗口和设置页问题优先看 `LanMountainDesktop/Views/``ViewModels/` 与相关 `Services/`
- 插件加载与安装问题优先看 `LanMountainDesktop/plugins/`
- 组件元数据或可放置规则问题优先看 `LanMountainDesktop/ComponentSystem/`
### 常见问题
- 如果提示 SDK 版本不匹配,先检查 `dotnet --info`
- 如果视频或 WebView 能力异常,优先在 Windows 环境验证
- 如果需要重置本地配置,可删除 `%LOCALAPPDATA%\\LanMountainDesktop\\settings.json` 后重启
- 如果需要验证插件打包或本地 feed使用 `scripts/Pack-PluginPackages.ps1`
### Linux 录音依赖
如果在 Linux 上使用录音机或自习监测相关能力,需要安装音频库:
- Debian/Ubuntu`sudo apt install libportaudio2 libasound2`
- Fedora/RHEL`sudo dnf install portaudio-libs alsa-lib`
- Arch Linux`sudo pacman -S portaudio alsa-lib`
- Alpine Linux`sudo apk add portaudio alsa-lib`
### 打包入口
- 桌面宿主打包说明:`LanMountainDesktop/PACKAGING.md`
- 插件相关本地包生成:`scripts/Pack-PluginPackages.ps1`
- CI 和工作流说明:`.github/README.md` 与相关 workflow 文档
### 文档协作约定
- 产品信息更新到 `docs/PRODUCT.md`
- 架构边界更新到 `docs/ARCHITECTURE.md`
- 需求与实施拆解更新到 `.trae/specs/`
- AI 协作入口和代码地图更新到 `AGENTS.md``docs/ai/`
### Launcher 架构说明
LanMountainDesktop 使用 Launcher 作为唯一入口,负责版本目录选择、AirApp Runtime 预启动和主程序启动。更新检查、下载、应用和回滚全部由 Host 负责。
#### 目录结构
安装后的目录结构:
```
C:\Program Files\LanMountainDesktop\
├── LanMountainDesktop.Launcher.exe ← 唯一入口
├── app-1.0.0/ ← 版本目录
│ ├── .current ← 当前版本标记
│ ├── LanMountainDesktop.exe
│ └── ... (所有依赖)
├── app-1.0.1/ ← 新版本
│ ├── .partial ← 下载中标记
│ └── ...
└── .Launcher/ ← Launcher 数据
├── state/ ← OOBE 状态
├── update/incoming/ ← 更新缓存
└── snapshots/ ← 更新快照
```
#### 版本标记文件
- `.current` - 标记当前使用的版本
- `.partial` - 标记下载未完成的版本
- `.destroy` - 标记待删除的旧版本
#### 启动流程
1. 用户启动 `LanMountainDesktop.Launcher.exe`
2. Launcher 扫描 `app-*` 目录,选择最佳版本
3. 如果是首次启动,显示 OOBE 引导
4. 显示 Splash 启动动画
5. 预启动 AirApp Runtime
6. 启动主程序 `app-{version}/LanMountainDesktop.exe`
7. 主程序启动成功后附加 Host PID 给 AirApp Runtime并清理标记为 `.destroy` 的旧版本
#### 更新流程
1. Host 调用更新源检查更新并按频道过滤版本
2. Host 下载 PLONDS file map、签名和对象文件到 `.Launcher/update/incoming/`
3. Host 写入 `deployment.lock` 并调用 `UpdateInstallGateway`
4. Host 验证签名和文件 hash创建新 `app-*` 目录,切换 `.current`
5. Host 写入快照并清理 incoming旧版本按启动清理策略处理
#### 版本回退
```bash
运行主程序,打开设置页中的更新区域触发回滚。
```
回退会切换到上一个有效版本,并保留快照记录。
## English
Use `LanMountainDesktop.slnx` as the workspace entry point. The standard loop is `dotnet restore`, `dotnet build LanMountainDesktop.slnx -c Debug`, `dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj`, and `dotnet test LanMountainDesktop.slnx -c Debug`.
For packaging, see `LanMountainDesktop/PACKAGING.md`. For plugin package generation or local feed workflows, use `scripts/Pack-PluginPackages.ps1`.
In-app marketplace plugin installs use the Host data root. When `Extensions/Plugins` is writable, the package is downloaded and verified immediately, then queued and applied on the next Host startup before plugin discovery. When portable data lives under an administrator-protected install path, Host stages the download in a user-writable location and invokes the restricted `LanMountainDesktop.Launcher.exe plugin install --app-root <package-root>` maintenance command with UAC to copy into the configured data root.
**Launcher Architecture**: LanMountainDesktop uses a Launcher as the single entry point, responsible for version management, updates, and launching the main application. See the Chinese section above for detailed architecture documentation.
## VeloPack Release Assets
- Windows incremental release packaging now uses VeloPack native outputs (
eleases.win.json, *.nupkg).
- Host owns update check/download/apply/rollback orchestration. Launcher only selects and starts the current version; VeloPack is used for package generation.

View File

@@ -0,0 +1,34 @@
# Ecosystem Boundaries
This document defines ownership boundaries for the LanMountainDesktop plugin ecosystem.
## Source of Truth
- Host runtime and plugin loading: `LanMountainDesktop`
- Plugin SDK API baseline: `LanMountainDesktop`
- Shared contracts used by host and plugins: `LanMountainDesktop`
- Plugin market index and ecosystem metadata: `LanAirApp`
- Official sample plugin implementation and release artifacts: `LanMountainDesktop.SamplePlugin`
## What Stays in This Repository
- Host runtime code and desktop shell behavior
- Plugin runtime, loader, install coordination, and host integration
- Plugin SDK public interfaces, contracts, and registration helpers
- Host appearance and settings infrastructure
- Tests that validate host + SDK behavior
## What Should Not Be Maintained Here as Authoritative
- Market documentation as a canonical developer portal
- Market publishing metadata as canonical source
- Official sample plugin source and release pipeline
- External reference projects (for example ClassIsland) as dependencies
## Local Debugging Rule
When running a workspace build, plugin market index and related market assets must be resolved from the sibling repository path:
- `..\\LanAirApp\\airappmarket\\index.json`
The host should not depend on an embedded `LanAirApp` mirror inside this repository for workspace market resolution.

View File

@@ -0,0 +1,127 @@
# External IPC Architecture
## Scope
This document defines the current external integration IPC baseline for LanMountainDesktop.
- The delivery focus is external application integration, not plugin process isolation.
- `dotnetCampus.Ipc` is the single IPC foundation for Host public APIs, Launcher/OOBE startup notifications, and plugin-contributed external services.
- Process isolation remains a future track and stays documented in `docs/PLUGIN_PROCESS_ISOLATION_ARCHITECTURE.md`.
## Design Summary
The public IPC stack is split into two complementary layers:
1. Strongly typed public services
- Contracts are marked with `[IpcPublic]`.
- Host exposes service instances through `CreateIpcJoint<TContract>(instance)`.
- .NET clients connect once and obtain strong typed proxies through `CreateIpcProxy<TContract>(peer)`.
2. Routed notifications
- `JsonIpcDirectRoutedProvider.NotifyAsync` is used for one-way event delivery.
- Startup progress, loading-state updates, catalog changed events, and plugin live events all use routed notify IDs.
This keeps command/query calls explicit and strongly typed while still giving plugins and Launcher a lightweight event channel.
## Projects
- `LanMountainDesktop.Shared.IPC`
- Public IPC constants, routed notify IDs, DTOs, strong-typed public service contracts, host/client helpers, and DI registration helpers.
- `LanMountainDesktop`
- Runs `PublicIpcHostService`, exposes built-in public services, and folds plugin-contributed services into one external catalog.
- `LanMountainDesktop.Launcher`
- Connects to the Host public pipe and listens for startup and loading-state notifications instead of running a custom length-prefixed IPC server.
- `LanMountainDesktop.PluginSdk`
- Adds `IPluginPublicIpcContributor`, `IPluginPublicIpcBuilder`, and `AddPluginPublicIpc(...)`.
## Built-in Public Services
Current built-in `[IpcPublic]` contracts:
- `IPublicAppInfoService`
- Returns application metadata such as version, codename, process id, pipe name, and startup time.
- `IPublicShellControlService`
- Allows external .NET clients to query shell status, activate the shell, repair tray readiness, repair taskbar entry visibility, open settings, request restart, and request exit.
- `IPublicPluginCatalogService`
- Returns the merged public IPC catalog snapshot exposed by Host.
## Routed Notify IDs
Current fixed routed notify IDs:
- `lanmountain.catalog.changed`
- `lanmountain.launcher.startup-progress`
- `lanmountain.launcher.loading-state`
The fixed routed surface is intentionally small. Runtime variation happens in the service catalog and in plugin-contributed service instances, not in ad-hoc top-level route registration after startup.
## Host Lifecycle
`PublicIpcHostService` is started during Host application startup and remains the single external IPC entry point.
Responsibilities:
- Start a named `dotnetCampus.Ipc` provider.
- Register fixed request routes before `StartServer()`.
- Expose built-in strong-typed public services.
- Maintain the merged service catalog.
- Publish startup and loading-state notifications to connected clients.
- Accept plugin-contributed public services after plugin load.
## Launcher / OOBE Migration
Launcher no longer depends on the previous custom named-pipe length-prefixed protocol as the primary path.
- Host publishes `StartupProgressMessage` through `lanmountain.launcher.startup-progress`.
- Host publishes `LoadingStateMessage` through `lanmountain.launcher.loading-state`.
- Launcher connects as a normal public IPC client and subscribes to those routed notifications.
This means Splash/OOBE is now just another IPC consumer on the same base transport used by external integrators.
Launcher-to-launcher de-duplication is intentionally separate from Host Public IPC. The active Launcher coordinator uses a per-user local pipe and `startup-attempt.json` heartbeat so secondary Launchers attach to the coordinator before any host process can be started twice.
## Plugin Public IPC Contribution Model
Plugins can contribute new external IPC services in two ways:
1. Declarative registration
- `services.AddPluginPublicIpc<TContract, TImplementation>(...)`
2. Advanced contributor
- Register `IPluginPublicIpcContributor`
- Use `IPluginPublicIpcBuilder` to contribute services from plugin DI
At plugin load time the Host runtime:
- discovers `PluginPublicIpcServiceRegistration`
- executes `IPluginPublicIpcContributor`
- validates that contributed contracts are `[IpcPublic]` interfaces
- registers the resolved instances into `PublicIpcHostService`
- emits `lanmountain.catalog.changed`
Plugins can also inject `IExternalIpcNotificationPublisher` and translate internal DI/message-bus events into routed notifications such as:
- `lanmountain.plugin.{pluginId}.attendance.updated`
- `lanmountain.plugin.{pluginId}.status.changed`
## Service Catalog
The public catalog is represented by `PublicIpcCatalogSnapshot` and includes:
- built-in and plugin-provided public services
- contract type metadata
- optional object id
- owning `pluginId` for plugin services
- declared notify IDs
- current loaded/enabled plugin list
This catalog is available through:
- strong-typed public service `IPublicPluginCatalogService`
- fixed request route `lanmountain.catalog.get`
- routed notify `lanmountain.catalog.changed`
## Current Limitations
- Strong-typed proxy/joint support is .NET-first.
- Plugin service removal is still restart-bound. New services can be added at runtime, but service removal is not yet modeled as a live unload contract.
- Cross-language clients still need a .NET bridge or sidecar if they want to consume `[IpcPublic]` contracts directly.
- Plugin process isolation is not part of this delivery. That remains future work.

View File

@@ -0,0 +1,78 @@
# 主程序发现配置指南
Launcher 支持灵活的主程序发现机制,可以通过多种方式配置主程序路径。
## 发现优先级
1. **环境变量** (`LMD_HOST_PATH`) - 最高优先级
2. **配置文件** (`host-discovery.json`)
3. **开发模式保存的路径** - 通过调试窗口选择
4. **部署目录** (`app-*`)
5. **开发路径** - 自动搜索解决方案中的 bin 目录
6. **额外配置路径** - 自定义搜索路径
7. **递归搜索** - 如果启用
## 配置方式
### 1. 环境变量
设置 `LMD_HOST_PATH` 环境变量指向主程序可执行文件:
```powershell
$env:LMD_HOST_PATH = "C:\MyApp\LanMountainDesktop.exe"
```
### 2. 配置文件
在应用根目录创建 `host-discovery.json`
```json
{
"HostPath": "C:\\Custom\\Path\\LanMountainDesktop.exe",
"AdditionalPaths": [
"${AppRoot}/custom",
"${UserProfile}/dev/build",
"C:/Program Files/LanMountainDesktop/*"
]
}
```
### 3. 开发模式
在错误窗口中按 `Ctrl+Shift+D` 打开调试窗口,启用开发模式并选择自定义路径。路径会自动保存,下次启动时优先使用。
## 路径变量
配置文件支持以下变量:
- `${AppRoot}` - 应用根目录
- `${BaseDirectory}` - Launcher 所在目录
- `${UserProfile}` - 用户主目录
- `${LocalAppData}` - 本地应用数据目录
## 通配符支持
`AdditionalPaths` 支持通配符:
```json
{
"AdditionalPaths": [
"C:/Builds/*/LanMountainDesktop.exe",
"${AppRoot}/versions/*/app.exe"
]
}
```
## 递归搜索
启用递归搜索可以自动在子目录中查找主程序:
```csharp
var options = new HostDiscoveryOptions
{
RecursiveSearch = true,
MaxRecursionDepth = 3
};
```
注意:递归搜索可能影响启动性能,建议仅在必要时启用。

View File

@@ -0,0 +1,440 @@
# 图片推荐组件可行性分析报告
## 需求概述
开发一个新的**图片推荐组件**,具备以下特性:
- 最小尺寸:**2×2 cells**
- 支持在组件设置界面**更换图片源**
- 独立AXAML文件实现
---
## 可行性结论
**高度可行**。项目已具备完整的组件基础设施,包括设置编辑器系统、数据源切换机制。预计开发工作量 **6-10小时**
---
## 1. 现有基础设施分析
### 1.1 参考实现DailyArtworkWidget
`DailyArtworkWidget` 已具备图片展示 + 图片源切换功能,是最佳参考:
**组件定义** (`ComponentRegistry.cs`):
```csharp
new DesktopComponentDefinition(
BuiltInComponentIds.DesktopDailyArtwork,
"Daily Artwork",
"Image",
"Info",
MinWidthCells: 4, // 当前最小4×2
MinHeightCells: 2,
AllowStatusBarPlacement: false,
AllowDesktopPlacement: true)
```
**设置编辑器** (`DailyArtworkComponentEditor.axaml`):
```xml
<ComboBox x:Name="SourceComboBox" SelectionChanged="OnSourceSelectionChanged">
<ComboBoxItem Tag="Domestic" /> <!-- 国内镜像 -->
<ComboBoxItem Tag="Overseas" /> <!-- 海外镜像 -->
</ComboBox>
```
### 1.2 组件设置系统架构
```
用户点击设置
ComponentEditorWindow 打开
DesktopComponentEditorRegistry 查找编辑器
创建对应的 ComponentEditor (如 DailyArtworkComponentEditor)
编辑器通过 ComponentSettingsAccessor 读写配置
配置变更通知组件刷新
```
**关键接口**:
- `IComponentSettingsContextAware` - 组件接收设置上下文
- `ComponentEditorViewBase` - 编辑器基类,提供 `LoadSnapshot()` / `SaveSnapshot()`
- `ComponentSettingsSnapshot` - 统一配置存储模型
---
## 2. 技术实现方案
### 2.1 文件结构
```
LanMountainDesktop/
├── ComponentSystem/
│ ├── BuiltInComponentIds.cs # 添加组件ID常量
│ └── ComponentRegistry.cs # 注册组件定义
├── Views/
│ ├── Components/
│ │ ├── ImageRecommendationWidget.axaml # 新组件UI
│ │ ├── ImageRecommendationWidget.axaml.cs # 新组件逻辑
│ │ └── DesktopComponentRuntimeRegistry.cs # 注册运行时
│ └── ComponentEditors/
│ ├── ImageRecommendationComponentEditor.axaml # 设置编辑器UI
│ ├── ImageRecommendationComponentEditor.axaml.cs # 设置编辑器逻辑
│ └── DesktopComponentEditorRegistryFactory.cs # 注册编辑器
├── Services/
│ ├── IRecommendationDataService.cs # 添加查询接口
│ └── RecommendationDataService.cs # 实现数据获取
└── Models/
└── ComponentSettingsSnapshot.cs # 添加配置字段
```
### 2.2 组件定义 (2×2最小尺寸)
```csharp
// BuiltInComponentIds.cs
public const string DesktopImageRecommendation = "DesktopImageRecommendation";
// ComponentRegistry.cs
new DesktopComponentDefinition(
BuiltInComponentIds.DesktopImageRecommendation,
"Image Recommendation",
"Image",
"Info",
MinWidthCells: 2, // 最小2×2
MinHeightCells: 2,
AllowStatusBarPlacement: false,
AllowDesktopPlacement: true,
ResizeMode: DesktopComponentResizeMode.Proportional) // 保持比例
```
### 2.3 数据源配置设计
**配置模型** (`ComponentSettingsSnapshot.cs`):
```csharp
public sealed class ComponentSettingsSnapshot
{
// 现有字段...
// 新增:图片推荐组件配置
public string ImageRecommendationSource { get; set; } = ImageRecommendationSources.Bing;
public bool ImageRecommendationAutoRefreshEnabled { get; set; } = true;
public int ImageRecommendationAutoRefreshIntervalMinutes { get; set; } = 60;
}
public static class ImageRecommendationSources
{
public const string Bing = "bing"; // Bing每日图片
public const string Picsum = "picsum"; // Picsum随机图片
public const string Unsplash = "unsplash"; // Unsplash精选
public static string Normalize(string? value) => value?.ToLowerInvariant() switch
{
"picsum" => Picsum,
"unsplash" => Unsplash,
_ => Bing
};
}
```
### 2.4 Widget实现要点
```csharp
// ImageRecommendationWidget.axaml.cs
public partial class ImageRecommendationWidget : UserControl,
IDesktopComponentWidget,
IRecommendationInfoAwareComponentWidget,
IComponentSettingsContextAware, // 接收设置变更
IComponentPlacementContextAware
{
private string _imageSource = ImageRecommendationSources.Bing;
public void SetComponentSettingsContext(DesktopComponentSettingsContext context)
{
// 读取组件实例配置
var snapshot = context.ComponentSettingsAccessor
.LoadSnapshot<ComponentSettingsSnapshot>();
_imageSource = ImageRecommendationSources.Normalize(
snapshot?.ImageRecommendationSource);
// 刷新图片
_ = RefreshImageAsync();
}
private async Task RefreshImageAsync()
{
var query = new ImageRecommendationQuery
{
Source = _imageSource
};
var result = await _recommendationService
.GetImageRecommendationAsync(query);
if (result.Success && result.Data is not null)
{
await LoadImageAsync(result.Data.ImageUrl);
}
}
}
```
### 2.5 设置编辑器实现
```xml
<!-- ImageRecommendationComponentEditor.axaml -->
<UserControl xmlns="https://github.com/avaloniaui"
x:Class="LanMountainDesktop.Views.ComponentEditors.ImageRecommendationComponentEditor">
<StackPanel Spacing="16">
<!-- 图片源选择 -->
<Border Classes="component-editor-card" Padding="20">
<StackPanel Spacing="12">
<TextBlock x:Name="SourceLabelTextBlock"
Classes="component-editor-section-title" />
<ComboBox x:Name="SourceComboBox"
Classes="component-editor-select"
HorizontalAlignment="Stretch"
SelectionChanged="OnSourceSelectionChanged">
<ComboBoxItem x:Name="BingItem" Tag="bing" />
<ComboBoxItem x:Name="PicsumItem" Tag="picsum" />
<ComboBoxItem x:Name="UnsplashItem" Tag="unsplash" />
</ComboBox>
</StackPanel>
</Border>
<!-- 自动刷新设置 -->
<Border Classes="component-editor-card" Padding="20">
<StackPanel Spacing="12">
<ToggleSwitch x:Name="AutoRefreshToggle"
Toggled="OnAutoRefreshToggled" />
<NumericUpDown x:Name="IntervalNumeric"
Minimum="5"
Maximum="1440"
ValueChanged="OnIntervalChanged" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>
```
```csharp
// ImageRecommendationComponentEditor.axaml.cs
public partial class ImageRecommendationComponentEditor : ComponentEditorViewBase
{
public ImageRecommendationComponentEditor(DesktopComponentEditorContext? context)
: base(context)
{
InitializeComponent();
ApplyState();
}
private void ApplyState()
{
// 本地化
SourceLabelTextBlock.Text = L("imgrec.settings.source", "Image Source");
BingItem.Content = L("imgrec.settings.bing", "Bing Daily");
PicsumItem.Content = L("imgrec.settings.picsum", "Random (Picsum)");
UnsplashItem.Content = L("imgrec.settings.unsplash", "Unsplash");
// 加载当前配置
var snapshot = LoadSnapshot();
var source = ImageRecommendationSources.Normalize(snapshot.ImageRecommendationSource);
SourceComboBox.SelectedItem = source switch
{
ImageRecommendationSources.Picsum => PicsumItem,
ImageRecommendationSources.Unsplash => UnsplashItem,
_ => BingItem
};
}
private void OnSourceSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressEvents) return;
var source = SourceComboBox.SelectedItem is ComboBoxItem item && item.Tag is string tag
? ImageRecommendationSources.Normalize(tag)
: ImageRecommendationSources.Bing;
var snapshot = LoadSnapshot();
snapshot.ImageRecommendationSource = source;
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.ImageRecommendationSource));
}
}
```
### 2.6 数据服务扩展
```csharp
// IRecommendationDataService.cs
public sealed record ImageRecommendationQuery(
string? Source = null,
bool ForceRefresh = false);
public sealed record ImageRecommendationSnapshot(
string ImageUrl,
string? Title = null,
string? Description = null,
string? SourceName = null);
public interface IRecommendationInfoService
{
// 现有方法...
Task<RecommendationQueryResult<ImageRecommendationSnapshot>> GetImageRecommendationAsync(
ImageRecommendationQuery query,
CancellationToken cancellationToken = default);
}
```
```csharp
// RecommendationDataService.cs
public async Task<RecommendationQueryResult<ImageRecommendationSnapshot>> GetImageRecommendationAsync(
ImageRecommendationQuery query,
CancellationToken cancellationToken = default)
{
var source = ImageRecommendationSources.Normalize(query?.Source);
return source switch
{
ImageRecommendationSources.Picsum => await GetPicsumImageAsync(query, cancellationToken),
ImageRecommendationSources.Unsplash => await GetUnsplashImageAsync(query, cancellationToken),
_ => await GetBingImageAsync(query, cancellationToken)
};
}
private async Task<RecommendationQueryResult<ImageRecommendationSnapshot>> GetBingImageAsync(
ImageRecommendationQuery? query,
CancellationToken ct)
{
// Bing每日图片API
var url = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN";
// ... 解析返回获取图片URL
var imageUrl = $"https://cn.bing.com{imageData.Url}";
return RecommendationQueryResult<ImageRecommendationSnapshot>.Ok(
new ImageRecommendationSnapshot(imageUrl, imageData.Title, imageData.Copyright));
}
```
---
## 3. 2×2尺寸适配考虑
### 3.1 布局适配策略
```csharp
// ImageRecommendationWidget.axaml.cs
private void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
var scale = _currentCellSize / BaseCellSize;
// 2×2尺寸较小需要调整字体和间距
var isSmallSize = _currentCellSize * 2 < 120; // 小于120px视为小尺寸
if (isSmallSize)
{
// 小尺寸模式简化UI只显示图片
TitleTextBlock.IsVisible = false;
DescriptionTextBlock.IsVisible = false;
}
else
{
// 正常模式:显示图片+文字
TitleTextBlock.IsVisible = true;
TitleTextBlock.FontSize = Math.Clamp(16 * scale, 10, 20);
}
// 圆角随尺寸缩放
RootBorder.CornerRadius = new CornerRadius(12 * scale);
}
```
### 3.2 比例约束
```csharp
// MainWindow.ComponentSystem.cs 添加比例约束
if (string.Equals(componentId, BuiltInComponentIds.DesktopImageRecommendation, StringComparison.OrdinalIgnoreCase))
{
// 保持1:1比例正方形最小2×2
return SnapSpanToScaleRules(
span,
new ComponentScaleRule(WidthUnit: 1, HeightUnit: 1, MinScale: 2));
}
```
---
## 4. 开发工作量估算
| 任务 | 文件 | 预估工时 |
|------|------|----------|
| 添加组件ID | `BuiltInComponentIds.cs` | 5分钟 |
| 注册组件定义 | `ComponentRegistry.cs` | 10分钟 |
| 实现Widget UI | `ImageRecommendationWidget.axaml` | 1.5小时 |
| 实现Widget逻辑 | `ImageRecommendationWidget.axaml.cs` | 2小时 |
| 注册运行时 | `DesktopComponentRuntimeRegistry.cs` | 10分钟 |
| 实现设置编辑器UI | `ImageRecommendationComponentEditor.axaml` | 1小时 |
| 实现设置编辑器逻辑 | `ImageRecommendationComponentEditor.axaml.cs` | 1小时 |
| 注册编辑器 | `DesktopComponentEditorRegistryFactory.cs` | 15分钟 |
| 扩展数据服务接口 | `IRecommendationDataService.cs` | 15分钟 |
| 实现数据获取 | `RecommendationDataService.cs` | 1.5小时 |
| 添加配置字段 | `ComponentSettingsSnapshot.cs` | 15分钟 |
| 添加比例约束 | `MainWindow.ComponentSystem.cs` | 15分钟 |
| 添加本地化 | `Resources.resx` | 30分钟 |
| **总计** | | **8-10小时** |
---
## 5. 风险与缓解
| 风险 | 等级 | 缓解措施 |
|------|------|----------|
| 2×2尺寸下UI过于拥挤 | 中 | 实现响应式布局,小尺寸隐藏文字 |
| 图片源API不稳定 | 低 | 多源备选,本地缓存 |
| 图片加载慢影响体验 | 低 | 异步加载,占位图过渡 |
| 跨域问题 | 低 | 使用支持CORS的源或后端代理 |
---
## 6. 建议图片源
| 源 | URL示例 | 特点 |
|----|---------|------|
| **Bing每日图片** | `https://cn.bing.com/HPImageArchive.aspx` | 高质量,每日更新 |
| **Picsum** | `https://picsum.photos/400/400` | 随机图片,稳定快速 |
| **Unsplash Source** | `https://source.unsplash.com/400x400` | 精选摄影,高质量 |
---
## 7. 结论
### 7.1 可行性评级: **A级 (强烈推荐)**
| 维度 | 评分 | 说明 |
|------|------|------|
| 技术成熟度 | ★★★★★ | DailyArtworkWidget提供完整参考 |
| 开发成本 | ★★★★★ | 8-10小时模式清晰 |
| 2×2适配 | ★★★★☆ | 需响应式布局适配小尺寸 |
| 用户价值 | ★★★★★ | 图片组件是桌面美化核心需求 |
### 7.2 下一步行动
1. **确认图片源**选择1-3个稳定的图片API
2. **UI设计**确认2×2尺寸下的视觉呈现
3. **开发**:按文件清单逐项实现
4. **测试**:验证不同尺寸、不同数据源切换
---
## 附录: 关键代码参考
### DailyArtworkWidget (现有参考)
- `Views/Components/DailyArtworkWidget.axaml`
- `Views/Components/DailyArtworkWidget.axaml.cs`
### DailyArtworkComponentEditor (设置编辑器参考)
- `Views/ComponentEditors/DailyArtworkComponentEditor.axaml`
- `Views/ComponentEditors/DailyArtworkComponentEditor.axaml.cs`
### 组件注册 (参考模式)
- `Services/DesktopComponentEditorRegistryFactory.cs` 第69-71行

View File

@@ -0,0 +1,248 @@
# 信息推荐类组件引入可行性分析报告
## 执行摘要
**结论:高度可行**。阑山桌面已具备完善的信息推荐类组件基础设施,引入新组件的技术门槛低,开发成本可控。
---
## 1. 现有基础设施评估
### 1.1 组件系统架构
项目采用**分层组件架构**,信息推荐类组件属于 `Info` 分类:
```
LanMountainDesktop/ComponentSystem/
├── DesktopComponentDefinition.cs # 组件元数据定义
├── ComponentRegistry.cs # 组件注册中心
├── BuiltInComponentIds.cs # 内置组件ID常量
└── Extensions/ # 扩展组件支持
```
### 1.2 现有信息推荐类组件清单
| 组件ID | 名称 | 分类 | 尺寸 | 数据源 |
|--------|------|------|------|--------|
| `DesktopDailyPoetry` | 每日诗词 | Info | 4x2 | jinrishici.com |
| `DesktopDailyArtwork` | 每日画作 | Info | 4x2 | Art Institute API |
| `DesktopDailyWord` | 每日单词 | Info | 4x2 | Youdao API |
| `DesktopDailyWord2x2` | 每日单词(小) | Info | 2x2 | Youdao API |
| `DesktopCnrDailyNews` | 央广新闻 | Info | 4x2 | CNR RSS |
| `DesktopIfengNews` | 凤凰新闻 | Info | 4x4 | 凤凰网 |
| `DesktopJuyaNews` | 橘鸦早报 | Info | 4x4 | 橘鸦API |
| `DesktopBilibiliHotSearch` | B站热搜 | Info | 4x2 | Bilibili API |
| `DesktopBaiduHotSearch` | 百度热搜 | Info | 4x2 | 百度API |
| `DesktopStcn24Forum` | STCN论坛 | Info | 4x4 | SmartTeach Forum |
**分析**已有10个信息推荐类组件覆盖新闻、诗词、艺术、单词、热搜等类型证明该类别组件需求旺盛且技术路径成熟。
---
## 2. 技术实现路径
### 2.1 数据服务层
**位置**: `LanMountainDesktop/Services/IRecommendationDataService.cs`
```csharp
public interface IRecommendationInfoService
{
Task<RecommendationQueryResult<T>> GetXXXAsync(XXXQuery query, CancellationToken ct);
void ClearCache();
}
```
**已有能力**
- 统一的查询/结果模式 (`RecommendationQueryResult<T>`)
- 缓存机制 (按渠道/类型分桶缓存)
- 超时控制 (默认8秒)
- 错误处理标准化
### 2.2 组件实现层
**位置**: `LanMountainDesktop/Views/Components/`
**标准实现模式**
```csharp
public partial class XXXWidget : UserControl,
IDesktopComponentWidget, // 基础组件接口
IRecommendationInfoAwareComponentWidget // 推荐信息感知接口
{
private readonly IRecommendationInfoService _recommendationService;
private readonly DispatcherTimer _refreshTimer;
// 标准生命周期
// - 附加到视觉树时启动刷新
// - 分离时清理资源
// - 支持自动刷新配置
}
```
### 2.3 注册与集成
**步骤1**: 在 `BuiltInComponentIds.cs` 添加ID常量
```csharp
public const string DesktopNewInfoComponent = "DesktopNewInfoComponent";
```
**步骤2**: 在 `ComponentRegistry.cs` 注册元数据
```csharp
new DesktopComponentDefinition(
BuiltInComponentIds.DesktopNewInfoComponent,
"New Info Component",
"IconKey",
"Info", // 分类
MinWidthCells: 4,
MinHeightCells: 2,
AllowStatusBarPlacement: false,
AllowDesktopPlacement: true)
```
**步骤3**: 在 `DesktopComponentRuntimeRegistry.cs` 注册运行时
```csharp
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopNewInfoComponent,
"NewInfoComponent_DisplayName",
ctx => new NewInfoComponentWidget())
```
**步骤4**: 实现数据服务方法 (可选,如使用现有服务可跳过)
---
## 3. 开发工作量估算
### 3.1 最小可行实现 (MVP)
| 任务 | 文件 | 预估工时 |
|------|------|----------|
| 添加组件ID | `BuiltInComponentIds.cs` | 5分钟 |
| 注册组件定义 | `ComponentRegistry.cs` | 10分钟 |
| 注册运行时 | `DesktopComponentRuntimeRegistry.cs` | 10分钟 |
| 实现Widget | `Views/Components/NewInfoWidget.axaml` | 2-4小时 |
| 实现数据服务方法 | `RecommendationDataService.cs` | 1-2小时 |
| 添加本地化 | `Localization/Resources.resx` | 15分钟 |
| **总计** | | **4-8小时** |
### 3.2 参考实现
**简单组件** (如 `BaiduHotSearchWidget`): ~200行代码
**复杂组件** (如 `IfengNewsWidget`): ~600行代码
---
## 4. 扩展性评估
### 4.1 数据源扩展
**支持的接入方式**
1. **REST API** (如 Bilibili API)
2. **RSS Feed** (如 CNR RSS)
3. **网页抓取** (如凤凰网)
4. **第三方SDK** (可扩展)
**配置化选项** (`RecommendationApiOptions`):
```csharp
public sealed record RecommendationApiOptions
{
public string NewDataSourceUrl { get; init; }
public TimeSpan CacheDuration { get; init; } = TimeSpan.FromMinutes(20);
public TimeSpan RequestTimeout { get; init; } = TimeSpan.FromSeconds(8);
}
```
### 4.2 组件模板化
现有组件可按功能类型抽象模板:
| 模板类型 | 代表组件 | 特点 |
|----------|----------|------|
| 列表型 | IfengNews, BilibiliHotSearch | 滚动列表,支持点击跳转 |
| 卡片型 | DailyPoetry, DailyWord | 单条内容展示 |
| 画廊型 | DailyArtwork | 图片为主,支持缩放 |
| 混合型 | JuyaNews | 图文混排 |
---
## 5. 风险与缓解措施
### 5.1 技术风险
| 风险 | 等级 | 缓解措施 |
|------|------|----------|
| 数据源不稳定 | 中 | 实现本地缓存 + 降级显示 |
| API限流 | 低 | 统一请求间隔控制 (已存在) |
| 跨域问题 | 低 | 使用后端代理或CORS支持API |
### 5.2 维护风险
| 风险 | 等级 | 缓解措施 |
|------|------|----------|
| 数据源API变更 | 中 | 抽象数据适配层,隔离变化 |
| 组件数量膨胀 | 低 | 考虑插件化迁移 |
---
## 6. 建议方案
### 6.1 短期方案 (推荐)
**直接添加内置组件**,遵循现有模式:
```
优点:
- 开发成本低 (4-8小时/组件)
- 与现有系统无缝集成
- 用户体验一致
适用场景:
- 核心信息源 (如官方新闻、学习资源)
- 高频使用组件
```
### 6.2 长期方案
**信息推荐组件插件化**
```
优点:
- 数据源可热插拔
- 社区可贡献组件
- 减小主程序体积
实现路径:
1. 定义信息推荐组件SDK接口
2. 提供组件模板脚手架
3. 市场发布审核流程
```
---
## 7. 结论
### 7.1 可行性评级: **A级 (强烈推荐)**
| 维度 | 评分 | 说明 |
|------|------|------|
| 技术成熟度 | ★★★★★ | 已有10个同类组件模式稳定 |
| 开发成本 | ★★★★★ | 4-8小时/组件,成本低 |
| 维护成本 | ★★★★☆ | 依赖外部API需持续维护 |
| 用户价值 | ★★★★★ | 信息类组件是桌面核心场景 |
| 扩展性 | ★★★★★ | 架构支持多种数据源 |
### 7.2 行动建议
1. **立即行动**: 选择1-2个高价值信息源进行试点开发
2. **建立规范**: 制定信息推荐组件开发SOP
3. **考虑插件化**: 当组件数量超过15个时评估插件化方案
---
## 附录: 参考文档
- `docs/ARCHITECTURE.md` - 系统架构概述
- `docs/ECOSYSTEM_BOUNDARIES.md` - 生态边界定义
- `LanMountainDesktop/ComponentSystem/README.md` - 组件系统说明
- `LanMountainDesktop/Services/IRecommendationDataService.cs` - 数据服务接口

View File

@@ -0,0 +1,556 @@
# 橘鸦新闻组件 UI 设计文档
## 1. 数据源分析
### RSS 结构
```xml
<item>
<title>2026-03-23</title> <!-- 日期作为标题 -->
<link>https://imjuya.github.io/juya-ai-daily/issue-37/</link>
<description>AI 早报 2026-03-23 视频版...</description>
<content:encoded>
<![CDATA[
<img src="封面图片URL" alt=""> <!-- 每日封面图 -->
<h1>AI 早报 2026-03-23</h1>
<p><strong>视频版</strong>: B站链接 | YouTube链接</p>
<h2>要闻</h2>
<ul>
<li>微信正式推出ClawBot插件... #1</li>
</ul>
<h2>开发者</h2>
<ul>
<li>Claude Code 测试新功能... #2</li>
</ul>
...更多分类
]]>
</content:encoded>
<pubDate>Mon, 23 Mar 2026 00:34:38 +0000</pubDate>
</item>
```
### 推送时间规律
- **推送时间**: 每天凌晨 00:30 - 02:00 (UTC+0)
- **北京时间**: 每天上午 08:30 - 10:00
- **历史数据**: RSS包含约30天的历史数据(从2026-02-18开始)
- **更新频率**: 每日一期,一期多条新闻
### 内容结构
每期早报包含:
1. **封面图片** - 每日独特的封面图
2. **视频版链接** - B站和YouTube双平台
3. **要闻** - 2-3条重要新闻
4. **开发者** - 技术相关动态
5. **产品发布** - 新产品/功能
6. **模型发布** - AI模型更新
7. **其他分类** - 投资、开源、研究等
---
## 2. 设计理念
### 品牌调性
- **橘鸦官网风格**: 柔和、温暖、阅读友好
- **主色调**: 砖红色/陶土色 (#bb5649) - 来自官网
- **背景色**: 米白色/奶油色 (#fefefe, #f8f5ec) - 柔和不刺眼
- **文字色**: 深灰蓝 (#34495e) - 温和专业
- **视觉风格**: 简洁优雅、阅读舒适、温暖亲切
### 设计关键词
- 柔和温暖
- 阅读友好
- 优雅简洁
- 舒适护眼
- **垂直连续滚动** ← 核心交互
---
## 3. 色彩方案 (参考橘鸦官网)
### 官网色彩提取
```
官网主色 (砖红/陶土): #bb5649
官网文字: #34495e
官网背景: #fefefe
官网次要背景: #f8f5ec (米黄/奶油)
官网引用块背景: rgba(192,91,77,.05)
官网引用块边框: rgba(192,91,77,.3)
官网链接悬停: #bb5649
官网元信息: #757575
```
### 日间模式 (Light Mode) - 柔和风格
| 元素 | 颜色 | 用途 |
|-----|------|------|
| 卡片背景 | #fefefe | 主卡片底色 (官网背景色) |
| 卡片边框 | #e6e6e6 | 细微边框 |
| 品牌标题 | #bb5649 | "橘鸦" 文字 (官网主色) |
| 日期标题 | #bb5649 | 日期大标题 |
| 新闻标题 | #34495e | 新闻条目文字 |
| 分类标签 | #bb5649 | 要闻/开发者等 |
| 时间戳 | #757575 | 发布时间 |
| 悬停背景 | rgba(192,91,77,.05) | 条目悬停效果 |
| 分隔线 | #e6e6e6 | 日期分隔 |
| 加载提示 | #757575 | 加载更多提示 |
### 夜间模式 (Dark Mode) - 柔和暗色
| 元素 | 颜色 | 用途 |
|-----|------|------|
| 卡片背景 | #2d2a2a | 深暖灰 |
| 卡片边框 | #3d3a3a | 细微边框 |
| 品牌标题 | #d4736a | 柔和砖红 |
| 日期标题 | #d4736a | 日期大标题 |
| 新闻标题 | #e8e4e0 | 新闻条目文字 |
| 分类标签 | #d4736a | 要闻/开发者等 |
| 时间戳 | #9a9590 | 次要信息 |
| 悬停背景 | rgba(212,115,106,.1) | 条目悬停效果 |
| 分隔线 | #3d3a3a | 日期分隔 |
| 加载提示 | #9a9590 | 加载更多提示 |
---
## 4. 布局设计
### 组件尺寸
- **默认尺寸**: 4格宽 x 4格高
- **最小尺寸**: 4格宽 x 4格高
- **滚动方向**: 垂直滚动
### 垂直连续滚动布局
```
┌─────────────────────────────────────────┐
│ 🧱 橘鸦 · AI早报 [🔗 官网] │ ← Header (固定或随滚动)
├─────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────┐ │
│ │ 📰 封面图 2026-03-23 │ │ ← 今天的新闻
│ │ │ │
│ └───────────────────────────────────┘ │
│ │
│ # 2026年3月23日 星期一 │ ← 日期大标题
│ │
│ ## 📌 要闻 │
│ • 微信正式推出ClawBot插件... │
│ • OpenAI发布GPT-5.4预览版... │
│ │
│ ## 💻 开发者 │
│ • Claude Code测试新功能... │
│ • 阶跃星辰推出StepPlan... │
│ │
│ 📺 视频版: B站 | YouTube │
│ │
│ ───────────────────────────────────── │ ← 日期分隔线
│ │
│ ┌───────────────────────────────────┐ │
│ │ 📰 封面图 2026-03-22 │ │ ← 昨天的新闻
│ │ │ │ (往下滑动显示)
│ └───────────────────────────────────┘ │
│ │
│ # 2026年3月22日 星期日 │
│ │
│ ## 📌 要闻 │
│ • OpenAI发布GPT-5.4... │
│ • Google推出新功能... │
│ │
│ ## 💻 开发者 │
│ • Anthropic更新Claude... │
│ │
│ 📺 视频版: B站 | YouTube │
│ │
│ ───────────────────────────────────── │
│ │
│ ┌───────────────────────────────────┐ │ ← 前天的新闻
│ │ 📰 封面图 2026-03-21 │ │ (继续往下滑动)
│ │ │ │
│ └───────────────────────────────────┘ │
│ │
│ # 2026年3月21日 星期六 │
│ │
│ ... │
│ │
│ ───────────────────────────────────── │
│ │
│ 正在加载更多... ↓ │ ← 加载提示
│ │
└─────────────────────────────────────────┘
```
### 日期分隔设计
```
┌─────────────────────────────────────────┐
│ │
│ ─────────── 3月22日 星期日 ─────────── │ ← 日期分隔条
│ │
│ [昨天的新闻内容] │
│ │
└─────────────────────────────────────────┘
```
### 单期新闻结构
```
┌─────────────────────────────────────────┐
│ │
│ [封面图 - 16:9 比例] │
│ │
│ # 2026年3月23日 星期一 │ ← 日期大标题
│ │
│ ## 📌 要闻 │ ← 分类标题
│ • 新闻条目1 │
│ • 新闻条目2 │
│ │
│ ## 💻 开发者 │
│ • 新闻条目3 │
│ • 新闻条目4 │
│ │
│ ## 🚀 产品发布 │
│ • 新闻条目5 │
│ │
│ 📺 视频版: [B站] [YouTube] │ ← 视频链接
│ │
└─────────────────────────────────────────┘
```
---
## 5. 字体规范
### 字体族
```xml
FontFamily="MiSans VF, avares://LanMountainDesktop/Assets/Fonts#MiSans"
```
### 字号规范
| 元素 | 字号 | 字重 | 说明 |
|-----|------|------|------|
| 品牌标题 | 20px | SemiBold | 顶部固定标题 |
| 日期大标题 | 22px | Bold | 每期日期 |
| 分类标题 | 16px | SemiBold | 要闻/开发者等 |
| 新闻条目 | 14px | Regular | 主要阅读内容 |
| 视频链接 | 13px | Regular | 底部视频入口 |
| 加载提示 | 13px | Regular | 加载更多 |
---
## 6. 核心交互: 垂直连续滚动
### 滚动行为
```
用户往下滑动
显示今天的新闻内容
继续往下滑动
显示日期分隔线
显示昨天的新闻内容
继续往下滑动
显示前天的新闻内容
...
到达已加载内容的底部
显示"正在加载更多..."
自动加载更早的新闻
```
### 加载策略
```csharp
// 初始加载: 最近3天的新闻
// 滚动到底部: 自动加载接下来3天
// 最大加载: 30天历史数据
// 内存管理: 只保留可视区域 ±3 天的数据
```
### 滚动位置记忆
```csharp
// 记录用户当前滚动位置
// 切换主题/刷新时不重置位置
// 下次打开组件时恢复到上次位置
```
---
## 7. 交互设计
### 悬停效果
```
新闻条目悬停:
- 背景色: 透明 → rgba(192,91,77,.05)
- 过渡时间: 200ms
- 光标: Hand cursor
```
### 点击效果
```
新闻条目点击:
- 打开浏览器跳转原文链接
- 轻微缩放: scale(0.98)
- 过渡时间: 100ms
```
### 封面图点击
```
封面图点击:
- 打开当期官网页面
- 轻微放大效果
```
### 日期标题点击
```
日期标题点击:
- 展开/收起该期新闻
- 箭头图标旋转动画
```
---
## 8. 动画效果
### 滚动动画
```
内容跟随滚动:
- 自然滚动,无额外动画
- 保持流畅 60fps
```
### 加载动画
```
新内容加载:
- 淡入: opacity 0 → 1 (300ms)
- 缓动: ease-out
```
### 日期分隔线动画
```
日期分隔线进入视口:
- 轻微放大: scale(0.95) → scale(1)
- 透明度: 0.5 → 1
- 时长: 200ms
```
---
## 9. 响应式适配
### 缩放规则
```csharp
scale = Math.Clamp(currentCellSize / 48, 0.56, 2.0)
: baseFontSize * scale
: baseSpacing * scale
```
### 最小尺寸保障
```
最小字体: 11px
最小间距: 8px
最小触摸区域: 44px
```
---
## 10. 代码结构预览
### XAML 结构
```xml
<UserControl>
<Border x:Name="RootBorder" CornerRadius="24" Background="#fefefe">
<Grid RowDefinitions="Auto,*">
<!-- Header (固定) -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="16">
<TextBlock Text="🧱 橘鸦 · AI早报"
Foreground="#bb5649" FontSize="20"/>
<Button x:Name="OfficialWebsiteButton" Grid.Column="1"
Content="🔗 官网" Click="OnOfficialWebsiteClick"
Background="Transparent" Foreground="#bb5649"/>
</Grid>
<!-- 滚动内容区 -->
<ScrollViewer Grid.Row="1" x:Name="ContentScrollViewer"
VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="NewsStackPanel">
<!-- 今天的新闻 -->
<local:DailyNewsView Date="2026-03-23"
CoverImageUrl="..."
Categories="..."/>
<!-- 日期分隔线 -->
<local:DateSeparator Date="2026-03-22"/>
<!-- 昨天的新闻 -->
<local:DailyNewsView Date="2026-03-22"
CoverImageUrl="..."
Categories="..."/>
<!-- 更多历史新闻... -->
<!-- 加载提示 -->
<TextBlock x:Name="LoadingMoreText"
Text="正在加载更多... ↓"
HorizontalAlignment="Center"
Margin="0,20"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>
</UserControl>
```
### DailyNewsView 组件
```xml
<!-- 单期新闻视图 -->
<Border x:Class="DailyNewsView" Margin="0,0,0,24">
<StackPanel>
<!-- 封面图 -->
<Border CornerRadius="12" ClipToBounds="True"
PointerPressed="OnCoverImageClick" Cursor="Hand">
<Image Source="{Binding CoverImageUrl}" Stretch="UniformToFill"/>
</Border>
<!-- 日期大标题 -->
<TextBlock Text="{Binding FormattedDate}"
FontSize="22" FontWeight="Bold"
Foreground="#bb5649" Margin="0,16,0,12"/>
<!-- 分类列表 -->
<ItemsControl ItemsSource="{Binding Categories}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,12">
<TextBlock Text="{Binding IconAndName}"
FontSize="16" FontWeight="SemiBold"
Foreground="#bb5649"/>
<ItemsControl ItemsSource="{Binding Items}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 视频链接 -->
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<TextBlock Text="📺 视频版:" Foreground="#757575"/>
<HyperlinkButton Content="B站" NavigateUri="{Binding BilibiliUrl}"/>
<TextBlock Text="|" Foreground="#757575" Margin="4,0"/>
<HyperlinkButton Content="YouTube" NavigateUri="{Binding YoutubeUrl}"/>
</StackPanel>
</StackPanel>
</Border>
```
---
## 11. 数据模型
```csharp
// 每日早报数据
public sealed record JuyaDailyNews(
DateTime Date,
string Title,
string CoverImageUrl,
string IssueUrl,
string BilibiliUrl,
string YoutubeUrl,
IReadOnlyList<JuyaNewsCategory> Categories,
DateTimeOffset FetchedAt);
// 新闻分类
public sealed record JuyaNewsCategory(
string Name,
string Icon,
IReadOnlyList<JuyaNewsItem> Items);
// 单条新闻
public sealed record JuyaNewsItem(
string Title,
string Url,
int? Number);
```
---
## 12. 与现有组件对比
| 特性 | CnrDailyNews | IfengNews | **JuyaNews (建议)** |
|-----|--------------|-----------|---------------------|
| 浏览方式 | 静态展示 | 静态展示 | **垂直连续滚动** |
| 历史查看 | 不支持 | 不支持 | **下滑自动加载** |
| 交互方式 | 点击刷新 | 点击刷新 | **滚动浏览** |
| 内容组织 | 平铺 | 平铺 | **按日期分组** |
---
## 13. 设计亮点
1. **垂直滚动**: 像社交媒体一样自然浏览
2. **连续阅读**: 今天→昨天→前天,无缝衔接
3. **日期分隔**: 清晰的日期标识,不会混淆
4. **自动加载**: 滑到底部自动加载更多历史
5. **柔和色彩**: 砖红色 + 米白色,阅读舒适
6. **主题适配**: 日间/夜间模式都柔和护眼
---
## 14. 实现建议
### 滚动加载实现
```csharp
public partial class JuyaNewsWidget : UserControl
{
private readonly List<JuyaDailyNews> _loadedNews = new();
private DateTime _earliestLoadedDate;
private bool _isLoadingMore;
private void OnScrollChanged(object? sender, ScrollChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender!;
// 检测是否滚动到底部
if (scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight - 100)
{
LoadMoreNews();
}
}
private async void LoadMoreNews()
{
if (_isLoadingMore) return;
_isLoadingMore = true;
// 加载接下来3天的新闻
var nextBatch = await FetchNewsBatch(_earliestLoadedDate.AddDays(-1), 3);
foreach (var news in nextBatch)
{
AddNewsToView(news);
_loadedNews.Add(news);
}
_earliestLoadedDate = nextBatch.Last().Date;
_isLoadingMore = false;
}
}
```
### 内存优化
```csharp
// 只保留可视区域附近的新闻
// 远离可视区域的新闻释放图片资源
// 保留文字内容,图片按需加载
```
---
*设计版本: v4.0*
*更新日期: 2026-03-24*
*更新内容: 改为垂直连续滚动浏览模式*

464
docs/archive/LAUNCHER.md Normal file
View File

@@ -0,0 +1,464 @@
# Launcher 架构文档
> LanMountainDesktop.Launcher - 应用启动器与版本目录选择
## 目录
- [概述](#概述)
- [职责范围](#职责范围)
- [架构设计](#架构设计)
- [核心服务](#核心服务)
- [版本管理](#版本管理)
- [启动流程](#启动流程)
- [命令行接口](#命令行接口)
- [开发指南](#开发指南)
## 概述
Launcher 是 LanMountainDesktop 的唯一入口点,负责:
- 首次体验引导 (OOBE)
- 启动动画 (Splash Screen)
- 多版本管理和选择
- 不承担更新职责;更新检查、下载、应用与回滚均由主程序负责
- 插件安装和升级维护命令
Air APP 窗口生命周期不再由 Launcher 进程内 broker 承担。Launcher 在正常启动时预启动包根下的 `LanMountainDesktop.AirAppRuntime`,该进程以框架依赖 JIT 方式运行并负责 Air APP IPC、实例表和 AirAppHost 进程管理。
**设计理念**: 参考 ClassIsland 项目,实现原子化的多版本管理和随时版本回退能力。
## 职责范围
### 1. OOBE (Out-of-Box Experience)
- 首次启动引导
- 欢迎页面
- 初始设置向导
### 2. Splash Screen
- 启动动画
- 加载进度显示
- 品牌展示
### 3. 版本管理
- 多版本并存 (`app-{version}/` 目录)
- 版本选择算法
- 版本标记系统 (`.current`, `.partial`, `.destroy`)
- 旧版本自动清理
### 4. 更新边界
- Host 负责更新检查、频道策略、下载、`deployment.lock` 写入、PLONDS 应用、部署切换和回滚
- Launcher 不提供 `update check` / `update download` / `apply-update` / `rollback` 命令
- Launcher 只按版本目录和 `.current` / `.partial` / `.destroy` 标记选择要启动的 Host
### 5. 插件维护
- `plugin install` / `plugin update` 保留为兼容维护命令
- 应用内插件市场下载、校验和 pending 队列由 Host 负责
## 架构设计
### 目录结构
**安装后的目录结构:**
```
C:\Program Files\LanMountainDesktop\
├── LanMountainDesktop.Launcher.exe ← 唯一入口
├── LanMountainDesktop.AirAppRuntime.exe ← Air APP 生命周期容器JIT
├── app-1.0.0/ ← 版本目录
│ ├── .current ← 当前版本标记
│ ├── LanMountainDesktop.exe
│ ├── LanMountainDesktop.AirAppHost.exe
│ ├── LanMountainDesktop.dll
│ └── ... (所有依赖)
├── app-1.0.1/ ← 新版本
│ ├── .partial ← 下载中标记
│ └── ...
├── app-0.9.9/ ← 旧版本
│ ├── .destroy ← 待删除标记
│ └── ...
└── .Launcher/ ← Launcher 数据目录
├── state/
│ └── first_run_completed ← OOBE 完成标记
├── update/
│ ├── incoming/ ← 更新缓存
│ │ ├── files.json
│ │ ├── files.json.sig
│ │ └── update.zip
│ └── public-key.pem ← RSA 公钥
└── snapshots/ ← 更新快照
└── {snapshot-id}.json
```
### 版本标记文件
| 文件名 | 作用 | 创建时机 | 删除时机 |
|--------|------|----------|----------|
| `.current` | 标记当前使用的版本 | 更新完成后 | 新版本激活时 |
| `.partial` | 标记下载未完成的版本 | 开始下载时 | 下载完成验证通过后 |
| `.destroy` | 标记待删除的旧版本 | 新版本激活时 | 目录删除后 |
## 核心服务
### DeploymentLocator
**职责**: 扫描和定位版本目录,选择最佳版本
**关键方法**:
```csharp
// 查找当前部署目录
string? FindCurrentDeploymentDirectory()
// 解析主程序可执行文件路径
string? ResolveHostExecutablePath()
// 获取当前版本号
string GetCurrentVersion()
// 构建下一个部署目录路径
string BuildNextDeploymentDirectory(string targetVersion)
// 清理标记为 .destroy 的目录
void CleanupDestroyedDeployments()
```
**版本选择算法**:
1. 扫描所有 `app-*` 目录
2. 过滤掉带 `.destroy``.partial` 标记的目录
3. 优先选择带 `.current` 标记的版本
4. 如果没有 `.current`,选择版本号最高的
### Host UpdateOrchestrator
**职责**: 更新检查、频道策略、manifest 解析、下载与安装触发位于 Host 的 `LanMountainDesktop/Services/Update/UpdateOrchestrator.cs`。Launcher 不再提供 `update check` / `update download` CLI。
### Host UpdateInstallGateway
**职责**: 更新应用与回滚入口位于 Host。`UpdateOrchestrator` 下载后调用 `UpdateInstallGateway` 在 Host 进程内应用 PLONDS payload回滚通过 Host 的 `UpdateRollbackGateway` 执行。
### LauncherOrchestrator / LaunchPipeline
**职责**: 协调完整的启动流程(`Shell/LauncherOrchestrator.cs` + `Startup/LaunchPipeline.cs`
**启动阶段 (ILaunchPhase)**:
1. `CleanupDeploymentsPhase` — 清理旧部署
2. `ExistingHostProbePhase` — 多实例 / 现有 Host 探测
3. `OobeGatePhase` — OOBE 步骤
4. `LaunchHostPhase` — 启动 Host
5. `MonitorStartupPhase` — IPC 启动监控
**GUI 入口**: `Shell/LauncherCompositionRoot` + `Shell/LauncherServiceRegistration`MS DI 轻量装配)
### ~~LauncherFlowCoordinator~~ (已移除)
已由 `LauncherOrchestrator` + `LaunchPipeline` 替代。
### OobeStateService
**职责**: 管理首次运行状态
**关键方法**:
```csharp
// 检查是否首次运行
bool IsFirstRun()
// 标记 OOBE 已完成
void MarkCompleted()
```
### PluginInstallerService
**职责**: CLI 维护命令下的插件包安装(`plugin install`)。应用内插件市场安装由 Host 在启动时应用 pending 队列,不经过 Launcher 正常启动流程。
**关键方法**:
```csharp
// 安装插件包CLI 维护)
LauncherResult InstallPackage(string sourcePath, string pluginsDirectory)
```
### PluginUpgradeQueueService
**职责**: CLI 维护命令下的待处理插件升级(`plugin update`。Launcher 正常 GUI 启动流程不再应用 pending 队列Host 在 `PluginRuntimeService.ApplyPendingPluginOperations()` 中统一处理。
**关键方法**:
```csharp
// 应用待处理的插件升级CLI 维护)
LauncherResult ApplyPendingUpgrades(string pluginsDirectory)
```
## 版本管理
### 版本选择算法详解
```csharp
public string? FindCurrentDeploymentDirectory()
{
var candidates = Directory.GetDirectories(rootDir, "app-*");
// 1. 过滤无效版本
var validCandidates = candidates
.Where(path =>
!File.Exists(Path.Combine(path, ".destroy")) &&
!File.Exists(Path.Combine(path, ".partial")))
.ToList();
// 2. 优先选择带 .current 标记的
var withMarkers = validCandidates
.Where(path => File.Exists(Path.Combine(path, ".current")))
.OrderByDescending(path => ParseVersion(path))
.FirstOrDefault();
if (withMarkers != null)
return withMarkers;
// 3. 选择版本号最高的
return validCandidates
.OrderByDescending(path => ParseVersion(path))
.FirstOrDefault();
}
```
### 版本激活流程
```csharp
private void ActivateDeployment(string fromDeployment, string toDeployment)
{
// 1. 在新版本添加 .current 标记
File.WriteAllText(Path.Combine(toDeployment, ".current"), string.Empty);
// 2. 移除旧版本的 .current 标记
var fromCurrent = Path.Combine(fromDeployment, ".current");
if (File.Exists(fromCurrent))
File.Delete(fromCurrent);
// 3. 标记旧版本为待删除
File.WriteAllText(Path.Combine(fromDeployment, ".destroy"), string.Empty);
// 4. 移除新版本的 .partial 标记 (如果有)
var toPartial = Path.Combine(toDeployment, ".partial");
if (File.Exists(toPartial))
File.Delete(toPartial);
}
```
### 版本清理流程
```csharp
public void CleanupDestroyedDeployments()
{
var destroyedDirs = Directory.GetDirectories(rootDir)
.Where(x => File.Exists(Path.Combine(x, ".destroy")));
foreach (var dir in destroyedDirs)
{
try
{
Directory.Delete(dir, recursive: true);
}
catch
{
// 忽略删除失败 (可能文件被占用)
// 下次启动时再试
}
}
}
```
## 启动流程
### 完整启动流程图
```
用户启动 Launcher.exe
清理旧版本 (.destroy 目录)
首次运行? ──Yes→ 显示 OOBE 窗口
↓ No
显示 Splash 窗口
检查待处理的更新
有更新? ──Yes→ 应用更新 (原子化)
↓ No
处理插件升级队列
选择最佳版本 (DeploymentLocator)
启动主程序 (Process.Start)
关闭 Splash 窗口
Launcher 退出
```
### 代码流程
**Program.cs**:
```csharp
static async Task<int> Main(string[] args)
{
var commandContext = CommandContext.FromArgs(args);
// 处理 CLI 命令
if (commandContext.Command != "launch")
return await Commands.RunCliCommandAsync(commandContext);
// 启动 Avalonia 应用
LauncherRuntimeContext.Current = commandContext;
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return Environment.ExitCode;
}
```
**App.axaml.cs / Shell 入口**:
```csharp
public override void OnFrameworkInitializationCompleted()
{
var splashWindow = LaunchEntryHandler.CreateSplashWindow();
splashWindow.Show();
_ = LauncherCompositionRoot.RunOrchestratorWithSplashAsync(desktop, context, splashWindow);
}
```
**LaunchPipeline**:
```csharp
internal sealed class LaunchPipeline
{
public async Task<LauncherResult> ExecuteAsync(LaunchContext context, CancellationToken cancellationToken = default)
{
foreach (var phase in _phases)
{
var result = await phase.ExecuteAsync(context, cancellationToken);
if (result.Status == LaunchPhaseStatus.Completed)
{
return result.Result!;
}
}
return LaunchResultBuilder.BuildFailure("launch", "pipeline_incomplete", "Launch pipeline finished without producing a result.");
}
}
```
`LauncherFlowCoordinator` 已删除。GUI 顶层生命周期由 `LauncherGuiCoordinator` 处理,启动阶段由 `LaunchPipeline` 和各 `ILaunchPhase` 承载;更新检查、下载、应用与回滚均由 Host 处理。
## 命令行接口
### launch - 启动应用
```bash
LanMountainDesktop.Launcher.exe launch
```
启动完整流程: OOBE → Splash → 更新 → 插件 → 主程序
### plugin install - 安装插件
```bash
LanMountainDesktop.Launcher.exe plugin install <path-to-plugin.laapp>
```
维护兼容入口:直接把 `.laapp` 插件包写入指定插件目录。应用内插件市场不再使用 Launcher 做普通插件安装;市场安装会先把包下载到当前用户的 pending 队列,并在下一次 Host 启动、插件发现前应用。
## 开发指南
### 本地调试
**直接运行 Launcher:**
```bash
dotnet run --project LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -- launch
```
**调试特定命令:**
```bash
# Launcher 不提供更新/回滚 CLI调试更新请运行主程序并使用设置页或 Host 更新服务。
```
### 模拟多版本环境
```bash
# 1. 发布主程序
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -c Debug -o ./test-deploy/app-1.0.0
# 2. 创建 .current 标记
New-Item -ItemType File -Path ./test-deploy/app-1.0.0/.current
# 3. 复制 Launcher 到根目录
Copy-Item LanMountainDesktop.Launcher/bin/Debug/net10.0/* ./test-deploy/
# 4. 运行 Launcher
./test-deploy/LanMountainDesktop.Launcher.exe launch
```
### 测试更新流程
```bash
# 1. 创建两个版本
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -o ./test-deploy/app-1.0.0
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -o ./test-deploy/app-1.0.1
# 2. 生成增量包
pwsh ./scripts/Generate-DeltaPackage.ps1 `
-PreviousVersion "1.0.0" `
-CurrentVersion "1.0.1" `
-PreviousDir "./test-deploy/app-1.0.0" `
-CurrentDir "./test-deploy/app-1.0.1" `
-OutputDir "./test-deploy/.Launcher/update/incoming"
# 3. 测试应用更新
# 运行主程序并通过 Host 更新服务触发下载、应用和回滚。
```
### 添加新的 OOBE 步骤
1. 实现 `IOobeStep` 接口:
```csharp
public class MyOobeStep : IOobeStep
{
public async Task RunAsync(CancellationToken cancellationToken)
{
// 显示 OOBE 窗口
// 等待用户完成
}
}
```
2.`Shell/LauncherOrchestrator.cs` 的 OOBE step 组装处注册,或通过后续 `ILaunchPhase`/OOBE 装配点接入:
```csharp
_oobeSteps = [
new WelcomeOobeStep(_oobeStateService),
new MyOobeStep() // 添加新步骤
];
```
当前内置 OOBE 向导窗口(`OobeWindow`)内步骤顺序包含:开场 → 主题 → **数据保存位置****启动与展示** → 隐私与遥测 → 完成。「启动与展示」写入 Host 的 `settings.json`PascalCase并在 Windows 下同步 Run 项,实现代码在 `HostAppSettingsOobeMerger.cs``LauncherWindowsStartupService.cs`,界面与逻辑挂在 `Views/OobeWindow.axaml(.cs)`
### 自定义更新源
更新源配置与 manifest provider 位于 Host 更新服务中,优先查看 `LanMountainDesktop/Services/Update/UpdateOrchestrator.cs``SettingsUpdateManifestProvider.cs` 与具体 provider。
## 相关文档
- [更新系统详细文档](UPDATE_SYSTEM.md)
- [构建和部署指南](BUILD_AND_DEPLOY.md)
- [架构文档](ARCHITECTURE.md)
- [开发文档](DEVELOPMENT.md)
## Current OOBE and Elevation Contract
- OOBE state is a per-user truth source stored at `%LOCALAPPDATA%\LanMountainDesktop\.launcher\state\oobe-state.json`.
- Same-user reinstall or upgrade must not re-enter OOBE.
- `first_run_completed` is legacy compatibility data only and should not remain the long-term primary format.
- Launch source values are `normal`, `postinstall`, `plugin-install`, and `debug-preview`.
- Auto-OOBE is allowed only for normal user-mode startup.
- `postinstall` may open OOBE only when the launcher is not elevated and the user state path is available.
- `plugin-install` and `debug-preview` must not auto-enter OOBE.
- Allowed elevation paths are limited to the installer itself, full installer update application, and user-confirmed legacy uninstall.
- Default plugin installation targets the Host data root and must not request elevation when that directory is writable.
- The Launcher `plugin install` maintenance command accepts `--app-root` so it can verify the configured data root before writing. It rejects targets outside that root.
- In-app market installs are deferred Host-side operations when the data root is writable: download and verify now, apply from the pending queue on the next Host startup.
- If portable data is configured under an administrator-protected install path, Host stages the package in a user-writable download directory and invokes the restricted Launcher maintenance command with UAC to copy the package into `Extensions/Plugins`.
## Public IPC Baseline
Launcher now consumes Host startup telemetry from the unified public IPC stack:
- Host publishes `StartupProgressMessage` via `lanmountain.launcher.startup-progress`
- Host publishes `LoadingStateMessage` via `lanmountain.launcher.loading-state`
- Launcher connects through `LanMountainDesktopIpcClient`
The previous custom length-prefixed named-pipe transport is no longer the primary startup communication path.
## Coordinator Guard
Launcher also owns a small per-user local coordinator used only between Launcher processes. It reserves `startup-attempt.json` before host launch, publishes a heartbeat, and exposes a local coordinator pipe for secondary Launchers. A secondary Launcher must attach to that coordinator or activate the existing Host through Public IPC instead of starting another Host process. See [Launcher Coordinator](LAUNCHER_COORDINATOR.md).

View File

@@ -0,0 +1,41 @@
# Launcher Coordinator
LanMountainDesktop Launcher uses a per-user coordinator to prevent duplicate host startup.
## Rules
- A Launcher reserves `%LocalAppData%\LanMountainDesktop\.launcher\state\startup-attempt.json` before starting the host.
- The active record stores coordinator pid, coordinator pipe name, heartbeat, host pid, Public IPC state, and shell status.
- Only the active coordinator may start the host process.
- Secondary Launchers attach to the coordinator and request desktop activation.
- A coordinator is considered live while its pid exists and its heartbeat is newer than `10s`.
- Normal launch probes Host Public IPC first; if the host is already running, Launcher activates it and exits.
## Tray And Taskbar
- Tray icon and tray menu are mandatory and are not controlled by user settings.
- Tray watchdog starts with the shell and runs until process exit.
- `ShowInTaskbar=true` affects only the main-window taskbar entry.
- When `ShowInTaskbar=true`, background mode uses a minimized taskbar entry while keeping tray visible.
- Pure `TrayOnly` is allowed only when `ShowInTaskbar=false` and tray is ready.
## Public Shell IPC
Launcher and external callers can use:
- `GetShellStatusAsync()`
- `ActivateMainWindowWithStatusAsync()`
- `EnsureTrayReadyAsync()`
- `EnsureTaskbarEntryAsync()`
These APIs report process, shell, tray, taskbar, and activation state separately so callers do not infer health from window visibility alone.
## Air APP Lifecycle
- `LanMountainDesktop.AirAppRuntime` is the Air APP lifecycle manager.
- The desktop host requests Air APP operations through `IAirAppLifecycleService` on the dedicated `LanMountainDesktop.AirAppRuntime.v1` IPC pipe.
- Launcher pre-starts `LanMountainDesktop.AirAppRuntime`; when the dedicated pipe is unavailable, the desktop host starts the runtime directly and retries the request.
- AirApp Runtime, not Launcher, owns the Air APP lifecycle IPC host and AirAppHost process table.
- AirApp Runtime creates, activates, tracks, and closes Air APP host processes by instance key: `{appId}:{sourceComponentId}:{sourcePlacementId}`.
- `LanMountainDesktop.AirAppHost` registers itself with AirApp Runtime after its window opens and unregisters on close; Runtime also prunes exited processes.
- Launcher waits only for the desktop host startup path. AirApp Runtime remains alive while Launcher/Host/requester or any Air APP process is alive, then exits after all are gone.

View File

@@ -0,0 +1,131 @@
# Launcher 打包分发指南
## 目录结构
打包给用户的 Launcher 应该包含以下结构:
```
LanMountainDesktop/
├── LanMountainDesktop.Launcher.exe # 启动器可执行文件
├── LanMountainDesktop.Launcher.dll # 启动器依赖
├── LanMountainDesktop.AirAppRuntime.exe # 轻应用生命周期容器JIT
├── ... # 其他启动器依赖文件
├── app-1.0.0/ # 主程序部署目录
│ ├── LanMountainDesktop.exe # 主程序可执行文件
│ ├── LanMountainDesktop.AirAppHost.exe # 轻应用窗口宿主
│ ├── LanMountainDesktop.dll # 主程序依赖
│ ├── version.json # 版本信息文件
│ └── .current # 当前版本标记文件
└── plugins/ # 插件目录(可选)
```
## 打包步骤
### 1. 构建 Launcher
```bash
dotnet build LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -c Release
```
### 2. 构建主程序
```bash
dotnet build LanMountainDesktop/LanMountainDesktop.csproj -c Release
```
### 3. 创建部署目录
```powershell
# 创建版本目录
New-Item -ItemType Directory -Path "dist/app-1.0.0" -Force
# 复制主程序文件
Copy-Item "LanMountainDesktop/bin/Release/net10.0/*" "dist/app-1.0.0/" -Recurse
# 创建版本标记
New-Item -ItemType File -Path "dist/app-1.0.0/.current" -Force
```
### 4. 复制 Launcher
```powershell
# 复制启动器文件
Copy-Item "LanMountainDesktop.Launcher/bin/Release/net10.0/*" "dist/" -Recurse
```
### 5. 创建安装包
可以使用以下工具创建安装包:
- **Inno Setup** - Windows 安装程序
- **WiX Toolset** - Windows Installer
- **MSIX** - Windows 应用包
- **Zip** - 便携版
## 用户数据存储位置
Launcher 会将用户配置存储在以下位置:
```
%LOCALAPPDATA%\LanMountainDesktop\.launcher\
├── devmode.config # 开发模式状态
└── custom-host-path.config # 自定义主程序路径
```
这些文件:
- **不会**随应用更新而删除
- **不会**随应用卸载而删除(除非用户手动清理)
- 在重装应用后会自动恢复之前的配置
## 生产环境行为
### 正常启动流程
1. 用户双击 `LanMountainDesktop.Launcher.exe`
2. Launcher 查找 `app-*` 目录中的主程序
3. 启动主程序并传递版本信息
4. 主程序显示正确的版本和开发代号
### 更新流程
1. 新版本下载到 `app-{new-version}/`
2. 创建 `.current` 标记指向新版本
3. 旧版本标记为 `.destroy`
4. 下次启动时自动使用新版本
## 开发环境配置
### 启用开发模式
1. 启动 Launcher如果找不到主程序会显示错误窗口
2.`Ctrl+Shift+D` 打开调试窗口
3. 勾选"启用开发模式"
4. 选择自定义主程序路径
5. 关闭窗口,配置会自动保存
### 开发模式优先级
开发模式的配置**不会**影响生产环境:
- 生产环境优先使用 `app-*` 目录
- 开发模式仅在找不到部署目录时生效
- 开发模式配置保存在用户数据目录,不影响其他用户
## 故障排除
### Launcher 找不到主程序
1. 检查 `app-*` 目录是否存在
2. 检查 `.current` 标记文件是否存在
3. 检查主程序可执行文件是否存在
4. 查看 `%LOCALAPPDATA%\LanMountainDesktop\.launcher\` 下的配置
### 版本信息不正确
1. 检查 `app-*/version.json` 是否存在
2. 检查 `version.json` 内容是否正确
3. 重新构建主程序生成新的 `version.json`
### 开发模式配置丢失
1. 检查 `%LOCALAPPDATA%\LanMountainDesktop\.launcher\` 目录权限
2. 检查磁盘空间是否充足
3. 手动删除配置目录后重新配置

View File

@@ -0,0 +1,36 @@
# Launcher Startup Visuals
This supplement records the startup rules that are shared by the launcher and the desktop host.
## Timeout behavior
- `30 seconds` is a soft timeout.
- Soft timeout means `still starting`, not `failed`.
- When the host process is alive or Public IPC is connected, Launcher keeps waiting and avoids launching another host process.
- `120 seconds` is the hard timeout for `desktop_not_visible`.
## Visual mode resolution
- `EnableSlideTransition = true` resolves to `SlideSplash` and forces `EnableFadeTransition = false`.
- `EnableSlideTransition = false` and `EnableFadeTransition = false` resolves to `StaticSplash`.
- `EnableSlideTransition = false` and `EnableFadeTransition = true` resolves to `Fade`.
## Fullscreen splash rules
- Fullscreen splash uses the shared `logo_nightly.png` asset.
- Slide splash enters from the right edge of the target screen and exits back to the right edge.
- Static splash uses the same fullscreen black surface without motion.
## Launcher splash image rules
- The hidden launcher debug menu can save a custom splash image.
- The selected image is copied into the Launcher data directory as `Launcher Picture.<ext>`.
- Supported formats are `.png`, `.jpg`, `.jpeg`, `.bmp`, `.gif`, and `.webp`; files larger than `10MB` are rejected.
- Splash displays the image with `Uniform` fitting, preserving the full image and allowing black letterboxing.
- The splash window uses a transparent self-drawn shell with a fixed Fluent `8px` outer corner radius.
## Recovery rules
- Closing Launcher during startup does not cancel the startup attempt.
- Relaunching Launcher attaches to the active attempt instead of spawning a second desktop process.
- If a host process is still alive during failure handling, Launcher offers activation or continued waiting before any retry.

View File

@@ -0,0 +1,433 @@
---
name: Launcher 单项目解耦
overview: 在保持单一 LanMountainDesktop.Launcher 项目、单一 exe、零部署风险的前提下按职责域增量重构目录分层、RunAsync→Pipeline+Phase、UpdateEngine→策略类、App→纯 Avalonia+LauncherOrchestrator执行过程中由 Agent 自主 Git 提交,每域可编译可测。
todos:
- id: phase-a-diagnostics
content: Phase AStartup 诊断 + HostStartupMonitor 独立类 + AOT 启动检测竞态修复 + 测试
status: completed
- id: phase-b-directory
content: Phase B1职责域目录迁移Deployment/Update/Startup/Oobe/Plugins/Infrastructure零逻辑变更提交
status: completed
- id: phase-b-pipeline
content: Phase B2RunAsync→LaunchPipeline+ILaunchPhase引入 LauncherOrchestrator删除 LauncherFlowCoordinator提交
status: completed
- id: phase-b-app-slim
content: Phase B3App.axaml.cs 精简为纯 Avalonia 初始化 + 委托 LauncherOrchestrator提交
status: completed
- id: phase-c-di
content: Phase CLauncherServiceRegistration + 轻量 MS DI统一 CLI/GUI 装配,提交
status: completed
- id: phase-d-update-split
content: Phase DUpdateEngineFacade→门面+策略类Verifier/Activator/Rollback 等),提交
status: completed
- id: phase-e-guardrails
content: Phase ELauncherArchitectureTests + 文档 + AOT 回归,提交
status: completed
isProject: false
---
# Launcher 单项目内部解耦改造计划(执行版)
## 0. 硬性约束
| 约束 | 说明 |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **单项目** | 仅 `[LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj](LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj)`,不新建 Launcher.* 独立程序集 |
| **单 exe** | 仍只发布 `LanMountainDesktop.Launcher.exe`AOT 单文件) |
| **零部署风险** | 不改变安装包目录结构、不引入新进程、不改变 Public IPC / Coordinator IPC 拓扑与契约 |
| **增量重构** | 一个职责域一域推进,每步 `dotnet build` + 相关 `dotnet test` 通过后再进下一步 |
| **单进程性能** | 模块间仅 in-process 接口调用,不为解耦新增 IPC |
| **未来可拆** | 各域暴露 `I`* 接口,将来若需多进程可直接复用契约 |
| **Git 自主提交** | Agent 在每个职责域完成且验证通过后 **自动 commit**,无需用户手动提交(见 §8 |
外部共享库 `[LanMountainDesktop.PluginPackaging](LanMountainDesktop.PluginPackaging/)` 保留Host + Launcher CLI 共用),不属于 Launcher 拆分。
---
## 1. 验收标准(必须全部满足)
### 1.1 零部署风险
- Inno Setup / CI 产物仍只有:`LanMountainDesktop.Launcher.exe` + `app-{version}/` + `.launcher/`
- Host 调用 Launcher 的 CLI 参数、`launch-source``apply-update` 路径不变
- Public IPC routes`lanmountain.launcher.startup-progress``loading-state`)与 Coordinator pipe 不变
- VeloPack / 更新 apply 状态机(`.current/.partial/.destroy`)行为不变
### 1.2 增量可验证
- 每个 Phase 结束:编译绿 + 该域新增/既有测试绿
- 允许「纯移动文件」的 PR 单独提交,行为 diff 为零
### 1.3 测试友好
- `Startup/``Update/``Deployment/` 内类型 **无 Avalonia 依赖**,可独立单元测试
- 每个 `ILaunchPhase`、每个 Update 策略类各有对应测试类
- 保留并扩展现有 `[LauncherStartupTimeoutPolicyTests](LanMountainDesktop.Tests/LauncherStartupTimeoutPolicyTests.cs)``[LauncherMultiInstancePolicyTests](LanMountainDesktop.Tests/LauncherMultiInstancePolicyTests.cs)`
### 1.4 启动性能
- Pipeline 阶段为同步/异步方法调用链,不引入额外进程或网络
- DI 容器仅在进程入口构建一次Stage/Phase 实例可复用 Singleton
### 1.5 代码结构目标
| 对象 | 当前(实测) | 目标 |
| ----------------------------------- | -------------------------------------------- | --------------------------------------------------- |
| `LauncherFlowCoordinator` 全 partial | ~1880 行859+568+279+…) | **删除**;逻辑迁入 Pipeline + Phases |
| `RunAsync()` 等价逻辑 | 跨 partial ~800+ 行 while/阶段混杂 | **≤80 行** 编排入口,细节在各 Phase |
| `UpdateEngineFacade` | ~1849 行 | 门面 **≤200 行** + Update 内部策略/基础设施类各 **≤300 行** |
| `App.axaml.cs` | ~258 行(已部分瘦身) | **≤120 行**:纯 Avalonia + 一行委托 `LauncherOrchestrator` |
| `LauncherOrchestrator` | 不存在(逻辑在 Coordinator + CompositionRoot 546 行) | **≤250 行**GUI 入口编排 |
| `LauncherCompositionRoot` | ~546 行 | **≤150 行**:仅 DI 构建 + 入口分发 |
---
## 2. 目标架构
### 2.1 核心类型关系
```mermaid
flowchart TB
Program --> EntryRouter
App --> LauncherOrchestrator
EntryRouter --> LauncherOrchestrator
LauncherOrchestrator --> LaunchPipeline
LaunchPipeline --> Phase1[CleanupPhase]
LaunchPipeline --> Phase2[OobeGatePhase]
LaunchPipeline --> Phase3[ApplyUpdatePhase]
LaunchPipeline --> Phase4[LaunchHostPhase]
LaunchPipeline --> Phase5[MonitorStartupPhase]
Phase3 --> IUpdateEngine
Phase4 --> IDeploymentLocator
Phase5 --> IHostStartupMonitor
LauncherCompositionRoot --> ServiceProvider
ServiceProvider --> LaunchPipeline
```
**命名约定:**
- `**LauncherOrchestrator`**GUI 生命周期内的唯一编排入口(取代 `LauncherFlowCoordinator` 对外角色)
- `**LaunchPipeline`**:按序执行 `ILaunchPhase` 列表
- `**ILaunchPhase**`:原 `ILaunchPipelineStage`;每个 Phase 对应原 `RunAsync` 中一个职责段
### 2.2 职责域目录(单项目内)
```
LanMountainDesktop.Launcher/
├── Program.cs # CLI / GUI 路由
├── App.axaml.cs # 纯 Avalonia≤120 行)
├── Shell/
│ ├── LauncherOrchestrator.cs # GUI 编排入口
│ ├── LauncherCompositionRoot.cs # DI + Entry 分发
│ ├── LaunchPipeline.cs
│ ├── Phases/ # ILaunchPhase 实现
│ │ ├── CleanupDeploymentsPhase.cs
│ │ ├── OobeGatePhase.cs
│ │ ├── ApplyPendingUpdatePhase.cs
│ │ ├── LaunchHostPhase.cs
│ │ └── MonitorStartupPhase.cs
│ └── EntryHandlers/ # apply-update / air-app-broker / attach
├── Deployment/
├── Update/
│ ├── IUpdateEngine.cs
│ ├── UpdateEngineFacade.cs # IUpdateEngine 薄门面
│ ├── PendingUpdateDetector.cs
│ ├── UpdateSignatureVerifier.cs
│ ├── LegacyUpdateApplier.cs
│ ├── PlondsUpdateApplier.cs
│ ├── DeploymentActivator.cs
│ ├── UpdateSnapshotStore.cs
│ ├── InstallCheckpointStore.cs
│ ├── RollbackStrategy.cs
│ └── IncomingArtifactsCleaner.cs
├── Startup/
├── Oobe/
├── Ipc/
├── AirApp/
├── Plugins/
├── Infrastructure/
├── Models/
└── Views/
```
### 2.3 模块依赖规则
- `Deployment/``Update/``Startup/`**禁止** `using Avalonia`
- `Views/`**禁止** 引用具体 `UpdateEngineFacade` / `DeploymentLocator`(仅接口或 Orchestrator
- 跨域:*仅通过 `I` 接口**Orchestrator/Pipeline 负责装配
### 2.4 与 Host 边界(不变)
| 能力 | Owner |
| -------------------------- | ------------------------------ |
| OOBE / Splash / 多实例 / 启动检测 | Launcher `Startup/` + `Shell/` |
| 更新 apply / rollback | Launcher `Update/` |
| 插件市场 / pending | Host + PluginPackaging |
| 更新 download | Host → spawn Launcher apply |
---
## 3. 三大核心拆分(用户指定)
### 3.1 拆分 `LauncherFlowCoordinator``RunAsync` → Pipeline + Phase
**现状:** 逻辑分散在 4 个 partial等效一个 1800+ 行 God Class`RunAsync` 内含清理、OOBE、更新、启动、IPC 监听、超时 while-loop、多实例分支。
**目标 API单项目 `Shell/` 内):**
```csharp
internal interface ILaunchPhase
{
string PhaseId { get; }
/// <returns>null = 继续下一阶段;非 null = 管道终止并返回结果</returns>
Task<LauncherResult?> ExecuteAsync(LaunchContext context, CancellationToken cancellationToken);
}
internal sealed class LaunchPipeline
{
public LaunchPipeline(IEnumerable<ILaunchPhase> phases) { ... }
public Task<LauncherResult> RunAsync(LaunchContext context, CancellationToken ct);
}
```
**Phase 映射(与原 RunAsync 步骤一一对应):**
| Phase | 原 RunAsync 段 | 产出 |
| ------------------------- | --------------------------------------- | ----------------------------- |
| `CleanupDeploymentsPhase` | `CleanupOldDeployments` | 无 UI |
| `ExistingHostProbePhase` | 多实例 / Public IPC 探测 | 可短路成功 |
| `ApplyPendingUpdatePhase` | `_updateEngine.ApplyPendingUpdateAsync` | 失败仍继续 |
| `OobeGatePhase` | migration + OOBE steps | UI via `ILauncherUiPresenter` |
| `LaunchHostPhase` | `LaunchHostWithIpcAsync` | Process + plan |
| `MonitorStartupPhase` | while-loop + IPC + timeout | 调用 `IHostStartupMonitor` |
`**LauncherOrchestrator` 职责:**
- 接收 `SplashWindow`、构建 `LaunchContext`(含 reporter、attempt registry、coordinator server
- 调用 `LaunchPipeline.RunAsync`
- 管理 Splash/Error 窗口生命周期(委托 `ILauncherUiPresenter`
- **不含** 更新/部署/IPC 细节
**删除清单:** `LauncherFlowCoordinator.cs` 及全部 partial 文件。
---
### 3.2 拆分 `UpdateEngineFacade` → 门面 + 策略类
**现状:**`UpdateEngineFacade` 为 1800+ 行单文件,混合检测、验签、解压、激活、快照、回滚、清理。
**目标结构:**
```
Update/
├── IUpdateEngine.cs # 对外契约(未来多进程可原样抽出)
├── UpdateEngineFacade.cs # 门面编排策略≤200 行
├── PendingUpdateDetector.cs # CheckPendingUpdate
├── UpdateSignatureVerifier.cs # manifest + RSA 签名 / hash
├── LegacyUpdateApplier.cs # Legacy zip apply
├── PlondsUpdateApplier.cs # PLONDS manifest apply
├── DeploymentActivator.cs # .current / .partial / .destroy
├── UpdateSnapshotStore.cs # snapshots 读写
├── InstallCheckpointStore.cs # resume checkpoint
├── RollbackStrategy.cs # rollback CLI/GUI
└── IncomingArtifactsCleaner.cs # CleanupIncomingArtifacts
```
**门面方法映射:**
| `IUpdateEngine` 公开方法 | 委托策略 |
| ---------------------------- | ------------------------------------------------------ |
| `CheckPendingUpdate()` | `PendingUpdateDetector` |
| `ApplyPendingUpdateAsync()` | Detector → Verifier → Legacy/PLONDS Applier → Activator → Snapshot/Checkpoint |
| `RollbackLatest()` | `RollbackStrategy` |
| `CleanupIncomingArtifacts()` | `IncomingArtifactsCleaner` |
| `DownloadAsync()`(若有) | 保持或拆 `UpdateDownloader` |
**测试:** 每个 Strategy 独立 mock `IDeploymentLocator` / 文件系统,不启 Avalonia。
---
### 3.3 精简 `App.axaml.cs` → 纯 Avalonia + `LauncherOrchestrator`
**现状:** ~258 行,仍含 apply-update、air-app-broker、preview、coordinator attach 等分支。
**目标结构:**
```csharp
// App.axaml.cs 目标形态(概念)
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var context = LauncherRuntimeContext.Current;
var mode = LauncherEntryModeResolver.Resolve(context);
_ = LauncherOrchestrator.RunAsync(desktop, context, mode);
}
base.OnFrameworkInitializationCompleted();
}
```
**从 App 迁出的逻辑 → `Shell/EntryHandlers/`**
| 现 App 分支 | 新 Handler |
| ----------------- | -------------------------------------- |
| `launch` + splash | `GuiLaunchEntryHandler` → Orchestrator |
| `apply-update` | `ApplyUpdateEntryHandler` |
| `air-app-broker` | `AirAppBrokerEntryHandler` |
| debug preview | `PreviewEntryHandler` |
**验收:** `App.axaml.cs` ≤120 行;不含 `new UpdateEngineFacade` / `new DeploymentLocator` / while-loop。
---
## 4. 分阶段执行顺序与 Git 提交点
```mermaid
flowchart LR
A[Phase A Startup] --> B1[Phase B1 目录迁移]
B1 --> B2[Phase B2 Pipeline+Orchestrator]
B2 --> B3[Phase B3 App 精简]
B3 --> C[Phase C DI]
B1 --> D[Phase D Update 策略拆分]
C --> E[Phase E 守卫+文档+AOT回归]
D --> E
```
### Phase AStartup 子系统 + AOT 生产 bug优先
- 抽出 `Startup/HostStartupMonitor.cs`(从 partial 独立)
- 修复 IPC 连接退避、成功判定统一走 `StartupSuccessTracker`
- Host 侧 `DesktopVisible` 上报对齐(仅日志/时序,不改 IPC 契约)
- 测试 + `**git commit**`: `fix(launcher): extract HostStartupMonitor and harden startup detection`
### Phase B1目录迁移零逻辑变更
- 物理移动文件到 `Deployment/``Update/``Startup/` 等,更新 namespace
- `dotnet build` + test
- `**git commit**`: `refactor(launcher): reorganize into responsibility folders`
### Phase B2Pipeline + Phase + LauncherOrchestrator
- 实现 `ILaunchPhase``LaunchPipeline``LauncherOrchestrator`
- 逐 Phase 从 Coordinator 迁移逻辑(可先并行运行对照测试)
- 删除 `LauncherFlowCoordinator*`
- `**git commit**`: `refactor(launcher): replace LauncherFlowCoordinator with LaunchPipeline`
### Phase B3App.axaml.cs 精简
- EntryHandlers 提取App 仅 Avalonia + Orchestrator 委托
- `**git commit**`: `refactor(launcher): slim App.axaml.cs to Avalonia shell only`
### Phase C轻量 DI
- `LauncherServiceRegistration.cs` + `Microsoft.Extensions.DependencyInjection`
- Program / CliHost / CompositionRoot 统一 `ServiceProvider`
- `**git commit**`: `refactor(launcher): add composition-root DI wiring`
### Phase DUpdateEngine 策略拆分(可与 B2 并行,依赖 B1
- 已完成:`UpdateEngineFacade` 收敛为 119 行 `IUpdateEngine` 门面
- 已完成:提取 pending 检测、签名、Legacy/PLONDS apply、激活、快照、checkpoint、回滚、incoming 清理等 Update 内部类
- 已完成:补 `UpdateStrategyTests` 覆盖关键策略行为
- `**git commit**`: `refactor(launcher): split UpdateEngine into strategy classes`
### Phase E守卫 + 文档 + AOT 回归
- 已完成:`LauncherArchitectureTests` 守住无 Avalonia 依赖、`IUpdateEngine` 依赖边界、门面/CompositionRoot 行数阈值
- 已完成:更新 `docs/LAUNCHER.md``docs/ARCHITECTURE.md``docs/DEVELOPMENT.md``docs/UPDATE_SYSTEM.md`
- 已验证Debug build、Launcher/Update/Architecture 过滤测试、全量测试AOT publish 本地 smoke 通过(保留现有 AOT/trim warnings
- `**git commit**`: `docs(launcher): document module boundaries and add architecture tests`
---
## 5. Phase / Service 测试矩阵
| 组件 | 测试文件 | 覆盖点 |
| ----------------------- | ---------------------------- | --------------------------------- |
| `StartupSuccessTracker` | `StartupSuccessTrackerTests` | Foreground/Tray/Background policy |
| `HostStartupMonitor` | `HostStartupMonitorTests` | 超时、IPC 延迟、ShellStatus 轮询 |
| `LaunchPipeline` | `LaunchPipelineTests` | Phase 短路、失败传播 |
| 各 `ILaunchPhase` | `*PhaseTests` | 单阶段 mock |
| `PendingUpdateDetector` | `PendingUpdateDetectorTests` | 无 pending / corrupt |
| `DeploymentActivator` | `DeploymentActivatorTests` | 标记文件状态机 |
| `RollbackStrategy` | `RollbackStrategyTests` | 快照回退 |
| 命名空间规则 | `LauncherArchitectureTests` | 无 Avalonia 泄漏 |
---
## 6. 明确不做
- 不新建 csprojLauncher.Deployment 等)
- 不新建 exe / Windows Service
- 不改变 Public IPC / Coordinator IPC 协议
- 不把插件市场安装迁回 Launcher
- 不为模块间通信引入新 IPC仅保留现有 Host↔Launcher 契约)
---
## 7. 风险与缓解
| 风险 | 缓解 |
| --------------- | ------------------------------------------------------------------ |
| 大规模移动 merge 冲突 | B1 独立 commit零逻辑变更 |
| Pipeline 迁移行为回归 | 先写 Phase 级测试再迁代码;保留 `LMD_LAUNCHER_LEGACY_COORDINATOR=1` 开关一个版本(可选) |
| AOT + DI | 显式注册,禁止反射扫描;`PublishAot` CI 步骤验证 |
| Update 拆分遗漏路径 | CLI `update *` 与 GUI apply-update 同一 `IUpdateEngine` 门面 |
---
## 8. Git 工作流Agent 自主提交)
**原则:** 每个 Phase 验证通过后立即提交;不累积巨型 uncommitted diff。
**Commit 前检查(每个 commit 必做):**
```bash
dotnet build LanMountainDesktop.slnx -c Debug
dotnet test LanMountainDesktop.slnx -c Debug --filter "FullyQualifiedName~Launcher"
```
**Commit message 风格(与仓库一致):**
```
refactor(launcher): replace LauncherFlowCoordinator with LaunchPipeline
Pipeline + Phase pattern; LauncherOrchestrator becomes GUI entry.
No deployment or IPC contract changes.
```
**禁止:** `git push --force`、修改 git config、跳过 hooks除非 hook 失败需修复后新 commit
**建议分支:** `refactor/launcher-internal-modularization`(单 long-lived 分支,按 Phase 连续 commit或每 Phase 一个 PR 由用户决定 merge 时机)。
---
## 9. 整体完成定义Definition of Done
-`LauncherFlowCoordinator` 源文件
- `App.axaml.cs` ≤120 行,仅 Avalonia + Orchestrator 委托
- `UpdateEngineFacade` 巨型文件已替换为薄门面 + Update 内部策略/基础设施类
- 职责域目录就位,架构测试通过
- 全量 Launcher 相关测试 + AOT publish smoke 通过
- 安装包结构与 IPC 拓扑与重构前一致
- 每个 Phase 有对应 Git commit工作区 clean

View File

@@ -0,0 +1,719 @@
# 插件开发指南
> 为 LanMountainDesktop 开发自定义插件
## 目录
- [快速开始](#快速开始)
- [插件架构](#插件架构)
- [创建插件](#创建插件)
- [插件生命周期](#插件生命周期)
- [添加组件](#添加组件)
- [添加设置页](#添加设置页)
- [使用服务](#使用服务)
- [打包和发布](#打包和发布)
- [最佳实践](#最佳实践)
## 快速开始
### 安装插件模板
```bash
# 安装官方插件模板
dotnet new install LanMountainDesktop.PluginTemplate
# 查看可用模板
dotnet new list | findstr lmd
```
### 创建新插件
```bash
# 创建插件项目
dotnet new lmd-plugin -n MyAwesomePlugin
# 进入项目目录
cd MyAwesomePlugin
# 还原依赖
dotnet restore
# 构建插件
dotnet build
```
### 项目结构
```
MyAwesomePlugin/
├── MyAwesomePlugin.csproj # 项目文件
├── Plugin.cs # 插件入口
├── Components/ # 组件目录
│ └── MyComponent.cs
├── Views/ # 视图目录
│ └── MyComponentView.axaml
├── ViewModels/ # 视图模型
│ └── MyComponentViewModel.cs
├── Settings/ # 设置页
│ └── MySettingsPage.axaml
└── plugin.json # 插件清单
```
## 插件架构
### 插件 SDK 版本
当前 SDK 版本: **5.0.0**
```xml
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="5.0.0" />
```
### 插件清单 (plugin.json)
```json
{
"Id": "com.example.myawesomeplugin",
"Name": "My Awesome Plugin",
"Version": "1.0.0",
"Author": "Your Name",
"Description": "A plugin that does awesome things",
"MinHostVersion": "1.0.0",
"Dependencies": [],
"Permissions": [
"FileSystem.Read",
"Network.Access"
]
}
```
### 核心接口
**IPlugin** - 插件入口接口:
```csharp
public interface IPlugin
{
string Id { get; }
string Name { get; }
string Version { get; }
Task InitializeAsync(IPluginContext context);
Task ShutdownAsync();
}
```
**IPluginContext** - 插件上下文:
```csharp
public interface IPluginContext
{
string PluginDirectory { get; }
IServiceProvider Services { get; }
ILogger Logger { get; }
ISettingsService Settings { get; }
}
```
## 创建插件
### 1. 实现插件入口
```csharp
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Shared.Contracts;
namespace MyAwesomePlugin;
public class Plugin : IPlugin
{
public string Id => "com.example.myawesomeplugin";
public string Name => "My Awesome Plugin";
public string Version => "1.0.0";
private IPluginContext? _context;
public async Task InitializeAsync(IPluginContext context)
{
_context = context;
// 注册组件
var componentRegistry = context.Services.GetService<IComponentRegistry>();
componentRegistry?.RegisterComponent<MyComponent>();
// 注册设置页
var settingsRegistry = context.Services.GetService<ISettingsPageRegistry>();
settingsRegistry?.RegisterPage<MySettingsPage>("我的插件设置");
// 初始化逻辑
context.Logger.LogInformation("Plugin initialized");
await Task.CompletedTask;
}
public async Task ShutdownAsync()
{
// 清理资源
_context?.Logger.LogInformation("Plugin shutting down");
await Task.CompletedTask;
}
}
```
### 2. 配置项目文件
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- 插件元数据 -->
<PluginId>com.example.myawesomeplugin</PluginId>
<PluginName>My Awesome Plugin</PluginName>
<PluginVersion>1.0.0</PluginVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
<PackageReference Include="LanMountainDesktop.Shared.Contracts" Version="5.0.0" />
<PackageReference Include="Avalonia" Version="12.0.1" />
</ItemGroup>
<!-- 复制 plugin.json 到输出目录 -->
<ItemGroup>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
```
## 插件生命周期
### 生命周期阶段
```
1. 发现 (Discovery)
2. 加载 (Load)
├─ 加载程序集
├─ 验证依赖
└─ 创建插件实例
3. 初始化 (Initialize)
├─ 调用 InitializeAsync()
├─ 注册组件
├─ 注册设置页
└─ 初始化服务
4. 运行 (Running)
├─ 组件渲染
├─ 事件处理
└─ 服务调用
5. 关闭 (Shutdown)
├─ 调用 ShutdownAsync()
├─ 清理资源
└─ 卸载程序集
```
### 生命周期钩子
```csharp
public class Plugin : IPlugin
{
// 插件加载后立即调用
public async Task InitializeAsync(IPluginContext context)
{
// 注册组件、服务、设置页
// 初始化资源
}
// 插件卸载前调用
public async Task ShutdownAsync()
{
// 保存状态
// 释放资源
// 取消订阅
}
}
```
## 添加组件
### 1. 定义组件类
```csharp
using LanMountainDesktop.PluginSdk.Components;
using LanMountainDesktop.Shared.Contracts;
namespace MyAwesomePlugin.Components;
[Component(
Id = "com.example.myawesomeplugin.mycomponent",
Name = "我的组件",
Description = "一个很棒的组件",
Category = "工具",
Icon = "avares://MyAwesomePlugin/Assets/icon.png"
)]
public class MyComponent : ComponentBase
{
public override string Id => "com.example.myawesomeplugin.mycomponent";
public override string Name => "我的组件";
// 组件设置
private string _message = "Hello, World!";
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
// 组件初始化
public override Task InitializeAsync()
{
// 加载设置
Message = Settings.GetValue("Message", "Hello, World!");
return Task.CompletedTask;
}
// 组件更新 (定时调用)
public override Task UpdateAsync()
{
// 更新组件数据
return Task.CompletedTask;
}
}
```
### 2. 创建组件视图
**MyComponentView.axaml:**
```xml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyAwesomePlugin.ViewModels"
x:Class="MyAwesomePlugin.Views.MyComponentView"
x:DataType="vm:MyComponentViewModel">
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="{Binding Component.Name}"
FontSize="18"
FontWeight="Bold" />
<TextBlock Text="{Binding Component.Message}"
TextWrapping="Wrap" />
<Button Content="点击我"
Command="{Binding ClickCommand}" />
</StackPanel>
</Border>
</UserControl>
```
**MyComponentView.axaml.cs:**
```csharp
using Avalonia.Controls;
namespace MyAwesomePlugin.Views;
public partial class MyComponentView : UserControl
{
public MyComponentView()
{
InitializeComponent();
}
}
```
### 3. 创建视图模型
```csharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyAwesomePlugin.ViewModels;
public partial class MyComponentViewModel : ObservableObject
{
[ObservableProperty]
private MyComponent _component;
public MyComponentViewModel(MyComponent component)
{
_component = component;
}
[RelayCommand]
private void Click()
{
Component.Message = "按钮被点击了!";
}
}
```
### 4. 注册组件
```csharp
public async Task InitializeAsync(IPluginContext context)
{
var componentRegistry = context.Services.GetService<IComponentRegistry>();
// 注册组件
componentRegistry?.RegisterComponent<MyComponent>(
componentFactory: () => new MyComponent(),
viewFactory: (component) => new MyComponentView
{
DataContext = new MyComponentViewModel((MyComponent)component)
}
);
}
```
## 添加设置页
### 1. 创建设置页视图
**MySettingsPage.axaml:**
```xml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyAwesomePlugin.Settings.MySettingsPage">
<StackPanel Spacing="16" Margin="24">
<TextBlock Text="我的插件设置"
FontSize="24"
FontWeight="Bold" />
<StackPanel Spacing="8">
<TextBlock Text="消息内容:" />
<TextBox x:Name="MessageTextBox"
Watermark="输入消息..." />
</StackPanel>
<Button Content="保存"
Click="SaveButton_Click" />
</StackPanel>
</UserControl>
```
**MySettingsPage.axaml.cs:**
```csharp
using Avalonia.Controls;
using Avalonia.Interactivity;
using LanMountainDesktop.PluginSdk;
namespace MyAwesomePlugin.Settings;
public partial class MySettingsPage : UserControl
{
private readonly ISettingsService _settings;
public MySettingsPage(ISettingsService settings)
{
InitializeComponent();
_settings = settings;
// 加载设置
MessageTextBox.Text = _settings.GetValue("Message", "Hello, World!");
}
private void SaveButton_Click(object? sender, RoutedEventArgs e)
{
// 保存设置
_settings.SetValue("Message", MessageTextBox.Text);
// 显示提示
// TODO: 显示保存成功提示
}
}
```
### 2. 注册设置页
```csharp
public async Task InitializeAsync(IPluginContext context)
{
var settingsRegistry = context.Services.GetService<ISettingsPageRegistry>();
settingsRegistry?.RegisterPage(
title: "我的插件",
category: "插件",
pageFactory: () => new MySettingsPage(context.Settings)
);
}
```
## 使用服务
### 可用服务
**ILogger** - 日志服务:
```csharp
context.Logger.LogInformation("信息日志");
context.Logger.LogWarning("警告日志");
context.Logger.LogError("错误日志");
```
**ISettingsService** - 设置服务:
```csharp
// 读取设置
var value = context.Settings.GetValue("Key", "DefaultValue");
// 写入设置
context.Settings.SetValue("Key", "NewValue");
// 监听设置变化
context.Settings.SettingChanged += (sender, e) =>
{
if (e.Key == "Key")
{
// 设置已变更
}
};
```
**INotificationService** - 通知服务:
```csharp
var notificationService = context.Services.GetService<INotificationService>();
notificationService?.ShowNotification(
title: "通知标题",
message: "通知内容",
type: NotificationType.Information
);
```
**IHttpClientFactory** - HTTP 客户端:
```csharp
var httpFactory = context.Services.GetService<IHttpClientFactory>();
var httpClient = httpFactory?.CreateClient();
var response = await httpClient.GetStringAsync("https://api.example.com/data");
```
## 打包和发布
### 1. 构建插件
```bash
dotnet build -c Release
```
### 2. 打包为 .laapp
```bash
# 使用官方打包脚本
pwsh ./scripts/Pack-PluginPackages.ps1 -PluginProject ./MyAwesomePlugin/MyAwesomePlugin.csproj
# 或手动打包
cd MyAwesomePlugin/bin/Release/net10.0
zip -r MyAwesomePlugin-1.0.0.laapp *
```
### 3. 测试插件
```bash
# 安装插件
LanMountainDesktop.Launcher.exe plugin install MyAwesomePlugin-1.0.0.laapp
# 启动应用测试
LanMountainDesktop.Launcher.exe launch
```
应用内插件市场不会调用 Launcher 安装插件。市场安装会把 `.laapp` 下载到当前用户的 pending 队列,并在下一次 Host 启动、插件发现前应用;上面的 Launcher 命令仅作为本地维护/兼容入口保留。
### 4. 发布插件
**选项 1: GitHub Release**
1. 创建 GitHub 仓库
2. 上传 `.laapp` 文件到 Release
3. 用户可以手动下载安装
**选项 2: 插件市场** (如果可用)
1. 提交插件到官方市场
2. 等待审核
3. 用户可以在应用内浏览和安装
## 最佳实践
### 性能优化
1. **避免阻塞 UI 线程:**
```csharp
// 错误
public override Task UpdateAsync()
{
Thread.Sleep(1000); // 阻塞!
return Task.CompletedTask;
}
// 正确
public override async Task UpdateAsync()
{
await Task.Delay(1000);
}
```
2. **使用异步 API:**
```csharp
// 使用 async/await
var data = await httpClient.GetStringAsync(url);
```
3. **缓存数据:**
```csharp
private string? _cachedData;
private DateTime _cacheTime;
public async Task<string> GetDataAsync()
{
if (_cachedData != null && DateTime.Now - _cacheTime < TimeSpan.FromMinutes(5))
return _cachedData;
_cachedData = await FetchDataAsync();
_cacheTime = DateTime.Now;
return _cachedData;
}
```
### 资源管理
1. **实现 IDisposable:**
```csharp
public class MyComponent : ComponentBase, IDisposable
{
private HttpClient? _httpClient;
public void Dispose()
{
_httpClient?.Dispose();
}
}
```
2. **取消订阅事件:**
```csharp
public override Task ShutdownAsync()
{
context.Settings.SettingChanged -= OnSettingChanged;
return Task.CompletedTask;
}
```
### 错误处理
1. **捕获异常:**
```csharp
public override async Task UpdateAsync()
{
try
{
await FetchDataAsync();
}
catch (HttpRequestException ex)
{
Logger.LogError(ex, "Failed to fetch data");
// 显示错误提示给用户
}
}
```
2. **验证输入:**
```csharp
public void SetUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentException("URL cannot be empty", nameof(url));
if (!Uri.TryCreate(url, UriKind.Absolute, out _))
throw new ArgumentException("Invalid URL format", nameof(url));
_url = url;
}
```
### 本地化
1. **使用资源文件:**
```csharp
// Resources/Strings.resx
// Name: ComponentName, Value: My Component
public override string Name => Resources.Strings.ComponentName;
```
2. **支持多语言:**
```xml
<!-- Resources/Strings.zh-CN.resx -->
<data name="ComponentName" xml:space="preserve">
<value>我的组件</value>
</data>
```
### 安全性
1. **验证用户输入:**
```csharp
// 防止路径遍历
var safePath = Path.GetFullPath(Path.Combine(pluginDirectory, userInput));
if (!safePath.StartsWith(pluginDirectory))
throw new SecurityException("Invalid path");
```
2. **使用 HTTPS:**
```csharp
// 强制使用 HTTPS
if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
throw new SecurityException("Only HTTPS URLs are allowed");
```
## 示例插件
查看官方示例插件:
- **天气组件** - 显示天气信息
- **倒计时组件** - 倒计时功能
- **RSS 阅读器** - 订阅和显示 RSS 源
仓库: https://github.com/YourOrg/LanMountainDesktop.SamplePlugin
## 相关文档
- [Plugin SDK v5 迁移指南](PLUGIN_SDK_V5_MIGRATION.md)
- [组件开发指南](COMPONENT_DEVELOPMENT.md)
- [API 参考](API_REFERENCE.md)
- [架构文档](ARCHITECTURE.md)
## Public IPC Extension
Plugins can now contribute external IPC capabilities through the Host public IPC entry point.
Recommended registration styles:
```csharp
services.AddPluginPublicIpc<IMyPluginPublicService, MyPluginPublicService>(
objectId: "default",
notifyIds: ["lanmountain.plugin.my-plugin.status.changed"]);
```
Or use the advanced contributor model:
```csharp
public sealed class MyPluginPublicIpcContributor : IPluginPublicIpcContributor
{
public void ConfigurePublicIpc(IPluginPublicIpcBuilder builder)
{
builder.AddService<IMyPluginPublicService>(
objectId: "default",
notifyIds: ["lanmountain.plugin.my-plugin.status.changed"]);
}
}
```
Additional notes:
- Public IPC contracts must be interfaces marked with `[IpcPublic]`.
- External .NET clients can reference the plugin contract assembly and create strong-typed proxies through the Host public pipe.
- Plugins can inject `IExternalIpcNotificationPublisher` to push live events outward through routed notifications.

View File

@@ -0,0 +1,263 @@
# 插件进程隔离架构
## 1. 背景与问题
当前插件系统只做了程序集隔离,没有做进程隔离。
- 宿主通过 `LanMountainDesktop/plugins/PluginRuntimeService.cs``LanMountainDesktop/plugins/PluginLoader.cs` 在 Host 进程内发现、加载并初始化插件。
- 插件依赖 `PluginLoadContext` 获得 `AssemblyLoadContext` 级别的依赖隔离,但代码、线程、托管堆和原生句柄仍与 Host 共处同一进程。
- 插件 `IHostedService` 也由 Host 直接构造并启动,所以插件后台逻辑和 Host 生命周期强耦合。
- 桌面组件、组件编辑器、设置页当前都直接返回 `Avalonia Control``SettingsPageBase`,并由 Host 直接插入视觉树。
这带来三个核心风险:
1. 插件崩溃会拖垮 Host典型场景包括 `StackOverflowException``AccessViolationException`、原生依赖崩溃。
2. 插件可直接访问 Host 进程中的服务实例与内存对象,缺少安全边界与权限审计点。
3. 现有“对象实例共享”模型难以迁移到跨进程,因为它默认调用成本近似于内存内方法调用。
## 2. 目标与非目标
### 2.1 一期目标
- 保持增量兼容,未声明新运行模式的插件继续走 `in-proc`
- 新增 `isolated-background` 运行模式,为每个隔离插件启动独立 Worker 进程。
- 把后台逻辑、定时任务、网络调用、原生高风险代码迁移到 Worker。
- UI 仍保留 Host 侧薄壳,通过 IPC 获取状态并发送命令。
- 新建独立 IPC 契约与封装层,为后续实际接线和插件升级提供稳定边界。
### 2.2 二期预留
- 预留 `isolated-window` 模式。
- 插件 UI 在进程外窗口中渲染Host 通过平台能力嵌入窗口句柄。
- Windows 侧可评估 `SetParent`Linux 侧可评估 `XEmbed` 或等价方案。
### 2.3 非目标
- 一期不强制所有插件升级。
- 一期不把现有 `IPluginExportRegistry``IPluginMessageBus` 直接升级成跨进程远程对象模型。
- 一期不实现完整的窗口嵌入渲染。
## 3. 运行模式设计
### 3.1 `in-proc`
- 默认模式。
- 继续使用当前 `PluginRuntimeService` + `PluginLoader` + `PluginLoadContext` 路径。
- 适合存量插件和仍依赖直接控件构造的插件。
### 3.2 `isolated-background`
- 一期目标模式。
- Host 为每个插件启动独立 Worker 进程。
- 启动时通过环境变量或命令行参数下发:
- `pluginId`
- `sessionId`
- `hostPipeName`
- `protocolVersion`
- `runtimeMode`
- Worker 内承载后台逻辑和 IPC 端点。
- Host 只保留 UI 壳层与状态同步逻辑。
### 3.3 `isolated-window`
- 二期预留模式。
- Worker 自己创建窗口并负责 UI 渲染。
- Host 负责窗口嵌入、生命周期协调、焦点与尺寸同步。
- 这是彻底切断插件 UI 崩溃影响 Host 的最终方案。
## 4. UI 方案取舍
### 4.1 方案一:进程外窗口
优点:
- 最强崩溃隔离。
- 插件 UI 不再进入 Host 视觉树。
- 安全边界更清晰。
缺点:
- 跨平台复杂度高。
- 窗口句柄嵌入、焦点管理、输入法、缩放、多屏和无障碍都需要额外设计。
- Avalonia 与平台窗口宿主的交互验证成本高。
### 4.2 方案二Host 薄 UI 壳层
优点:
- 与现有组件系统、编辑器系统、设置页系统的迁移成本最低。
- 可以先隔离最危险的后台与原生逻辑。
- 适合做增量兼容与插件生态迁移。
缺点:
- 如果 Host 仍执行插件提供的 UI 代码,仍有残余稳定性风险。
- 无法从根本上解决所有 UI 级崩溃。
### 4.3 一期结论
一期采用方案二,也就是 `isolated-background`
这意味着:
- 后台逻辑先隔离。
- UI 交互先代理。
- 文档必须明确残余风险。
- `isolated-window` 的架构接口要预留,但不进入一期实现。
## 5. IPC 协议设计
底层 IPC 继续基于 [dotnetCampus.Ipc](https://github.com/dotnet-campus/dotnetCampus.Ipc),但插件协议采用“显式路由 + DTO + 会话/心跳/故障管理”的方式,而不是把 Host 服务对象直接远程化。
### 5.1 路由分组
- `session/*`
- `session/handshake`
- `session/capabilities`
- `session/ready`
- `lifecycle/*`
- `lifecycle/initialize`
- `lifecycle/stop`
- `lifecycle/restart-request`
- `lifecycle/state-changed`
- `settings/*`
- `settings/get-snapshot`
- `settings/write`
- `settings/changed`
- `appearance/*`
- `appearance/get-snapshot`
- `appearance/changed`
- `ui/*`
- `ui/attach`
- `ui/detach`
- `ui/command`
- `ui/state-changed`
- `heartbeat/*`
- `heartbeat/ping`
- `heartbeat/pong`
- `log/*`
- `log/write`
- `fault/*`
- `fault/report`
### 5.2 契约原则
- 只传 DTO不传 Host 内存对象。
- 所有 handler 必须在 `StartServer()` 前注册完成。
- 使用 source-generated `System.Text.Json` 上下文统一序列化。
- 协议版本通过 `session/handshake` 协商。
- 能力通过显式 capability 列表声明和授予,不做隐式远程对象暴露。
### 5.3 明确不兼容的旧能力
- `IPluginExportRegistry` 的对象实例共享不延续到隔离模式。
- 现有 `IPluginMessageBus` 不作为隔离插件主通信通道。
- Worker 不直接创建 `Avalonia Control` 并返回给 Host。
## 6. 工程拆分
### 6.1 `LanMountainDesktop.PluginIsolation.Contracts`
职责:
- 纯 DTO
- 协议版本
- 路由常量
- 错误码
- capability 声明
- source-generated JSON context
约束:
- 不引用 Avalonia
- 不依赖 Host 服务实现
- 作为 Host、Worker、SDK 共享的传输边界
### 6.2 `LanMountainDesktop.PluginIsolation.Ipc`
职责:
- 对标 ClassIsland 的轻量 IPC 封装外壳
- 统一 `PluginIpcClient`
- 统一 `PluginIpcServer`
- 统一启动参数、环境变量、通知路由常量
约束:
- 借鉴 ClassIsland 的“包装层 + 常量集中 + 客户端低接入成本”
- 但不把插件系统主协议设计成大面积远程属性模型
### 6.3 `LanMountainDesktop.PluginSdk`
新增内容:
- `runtime.mode` Manifest 支持
- `PluginRuntimeMode`
- `IPluginWorker`
- `IPluginWorkerContext`
- `PluginWorkerBase`
- `[PluginWorkerEntrance]`
## 7. ClassIsland IPC 借鉴与取舍
参考资料:
- [ClassIsland 仓库](https://github.com/ClassIsland/ClassIsland)
- [ClassIsland.Shared.IPC/IpcClient.cs](https://github.com/ClassIsland/ClassIsland/blob/master/ClassIsland.Shared.IPC/IpcClient.cs)
- [ClassIsland.Shared.IPC/IpcRoutedNotifyIds.cs](https://github.com/ClassIsland/ClassIsland/blob/master/ClassIsland.Shared.IPC/IpcRoutedNotifyIds.cs)
- [ClassIsland.Shared.IPC Abstractions](https://github.com/ClassIsland/ClassIsland/tree/master/ClassIsland.Shared.IPC/Abstractions/Services)
借鉴点:
- IPC 能力独立成包,边界清晰。
- `IpcClient` 对底层库做轻量封装,接入成本低。
- 通知路由有集中定义,事件名稳定。
- 通过公共接口暴露很小的可用面,减少耦合。
不照搬的部分:
- 不把插件隔离主协议做成“远程对象/远程属性”模型。
- 不隐藏跨进程调用成本。
- 不让 UI 状态同步变成一串隐式属性访问。
最终结论:
- 采用“ClassIsland 风格的封装外壳”。
- 协议主线仍是显式路由和明确 DTO。
## 8. 迁移策略
### 8.1 Manifest
`plugin.json` 新增:
```json
{
"runtime": {
"mode": "in-proc"
}
}
```
默认值为 `in-proc`
### 8.2 插件迁移顺序
1. 保持现有 UI 注册方式不变。
2. 把后台任务和风险代码收敛到 Worker。
3. 让 Host UI 通过 `ui/*``settings/*` 路由访问 Worker 状态。
4. 在二期再评估 `isolated-window` 迁移。
## 9. 故障模型与残余风险
一期必须满足以下行为:
- Worker 启动失败时Host 仅禁用该插件并记录诊断。
- Worker 心跳超时或被强杀时Host 不崩溃。
- Worker 上报 `fault/report`Host 将插件标记为 degraded 或 faulted。
一期残余风险也必须明确写出:
- 如果 Host 仍执行插件提供的 UI 代码UI 级崩溃仍可能影响 Host。
- 因此 `isolated-background` 不是最终隔离形态,只是第一阶段收益最高的落点。
- 完整 UI 崩溃隔离依赖二期 `isolated-window`

View File

@@ -0,0 +1,182 @@
# Plugin SDK v4 Migration Guide
This guide describes the breaking changes introduced by Plugin SDK `4.0.0`.
## Version Baseline
- Host plugin SDK baseline: `4.0.0`
- Plugins targeting `3.x` are rejected by default
- Manifest file remains `plugin.json`
## Breaking Changes
1. `AddPluginDesktopComponent` now uses options-first registration.
2. `PluginDesktopComponentOptions` is now the canonical component registration shape and must include `ComponentId`.
3. Appearance and radius access are provided through strongly typed APIs:
- `IPluginAppearanceContext`
- `PluginAppearanceSnapshot`
- `PluginCornerRadiusTokens`
- `PluginCornerRadiusPreset`
4. `PluginDesktopComponentContext` now exposes `Appearance` as the primary appearance access point.
## New Component Registration Pattern
```csharp
services.AddPluginDesktopComponent<MyWidget>(new PluginDesktopComponentOptions
{
ComponentId = "YourPlugin.Widget",
DisplayName = "My Widget",
IconKey = "PuzzlePiece",
Category = "Plugins",
MinWidthCells = 4,
MinHeightCells = 3,
CornerRadiusPreset = PluginCornerRadiusPreset.Default
});
```
## Appearance Usage Pattern
```csharp
public MyWidget(PluginDesktopComponentContext context)
{
var mdRadius = context.Appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Md);
var adaptiveRadius = context.Appearance.ResolveScaledCornerRadius(12, 8, 20);
}
```
## Corner Radius System
Plugin widgets must follow the host's corner radius settings to maintain visual consistency with built-in components.
### Why Plugins Cannot Use XAML Resources
Plugins run in a separate `AssemblyLoadContext` and cannot directly access the host's resource dictionary. Therefore, `{DynamicResource DesignCornerRadiusComponent}` is not available in plugin XAML. Instead, plugins must resolve corner radius values in code through `PluginDesktopComponentContext`.
### Available Corner Radius Presets
| Preset | Default Value | Usage |
|--------|---------------|-------|
| `Micro` | 6px | Tiny elements |
| `Xs` | 12px | Small elements and icon containers |
| `Sm` | 14px | Small colored blocks |
| `Md` | 20px | Common buttons/cards |
| `Lg` | 28px | Normal glass panels |
| `Xl` | 32px | Emphasized containers |
| `Island` | 36px | Large containers |
| `Component` | 18px | **Desktop widget standard radius** |
| `Default` | (adaptive) | Adaptive based on cell size |
### Corner Radius API Reference
```csharp
public class MyWidget : Border
{
public MyWidget(PluginDesktopComponentContext context)
{
// Method 1: Use preset tokens (recommended for consistency)
CornerRadius = context.ResolveCornerRadius(PluginCornerRadiusPreset.Component);
// Method 2: Use preset with fallback (extension method)
CornerRadius = context.Appearance.Snapshot.ResolveCornerRadius(
PluginCornerRadiusPreset.Md,
fallback: new CornerRadius(8));
// Method 3: Custom radius with global scale applied
CornerRadius = context.ResolveScaledCornerRadius(baseRadius: 16, minimum: 8, maximum: 24);
// Method 4: Access tokens directly
var tokens = context.CornerRadiusTokens;
CornerRadius = tokens.ToCornerRadius(PluginCornerRadiusPreset.Md);
// Method 5: Get raw token value (double)
double componentRadius = context.CornerRadiusTokens.Component;
}
}
```
### Best Practices
1. **Always use `PluginCornerRadiusPreset.Component` for the widget root container** - This ensures consistency with built-in widgets.
2. **Apply corner radius in code, not XAML** - Since plugins cannot access host resources, set `CornerRadius` in the constructor or code-behind.
3. **Re-apply radius on size changes** - For adaptive layouts, subscribe to `SizeChanged` and recalculate:
```csharp
public MyWidget(PluginDesktopComponentContext context)
{
_context = context;
ApplyCornerRadius();
SizeChanged += (_, _) => ApplyCornerRadius();
}
private void ApplyCornerRadius()
{
var basis = Math.Min(Bounds.Width, Bounds.Height);
CornerRadius = _context.ResolveCornerRadius(
PluginCornerRadiusPreset.Component,
minimum: Math.Clamp(basis * 0.08, 8, 16),
maximum: Math.Clamp(basis * 0.15, 16, 28));
}
```
4. **Inner elements can use smaller presets** - For cards or buttons inside your widget:
```csharp
var innerCard = new Border
{
CornerRadius = _context.ResolveCornerRadius(PluginCornerRadiusPreset.Md)
};
```
## Manifest Update
Update plugin manifests to API `4.x`:
```json
{
"apiVersion": "4.0.0"
}
```
## Validation Checklist
- `plugin.json` declares `apiVersion` `4.0.0` (or compatible `4.x`)
- component registration migrated to options model
- runtime appearance access uses `IPluginAppearanceContext`
- plugin package rebuilt and republished as `.laapp`
## Process Isolation Additions
SDK `4.x` now also reserves manifest and API surface for process isolation without breaking existing plugins.
### Manifest
`plugin.json` can declare the desired runtime mode:
```json
{
"runtime": {
"mode": "in-proc"
}
}
```
Supported values:
- `in-proc`
- `isolated-background`
- `isolated-window`
If `runtime` is omitted, the host normalizes it to `in-proc` for backward compatibility.
### Worker Entry
Plugins that opt into isolated execution can prepare a worker-side entry by implementing:
- `IPluginWorker`
- `PluginWorkerBase`
- `IPluginWorkerContext`
- `[PluginWorkerEntrance]`
The first phase only targets `isolated-background`: background services, timers, network calls, and risky native integrations move into the worker process, while UI remains a host-side shell driven over IPC.

View File

@@ -0,0 +1,48 @@
# Plugin SDK v5 Migration Guide
Plugin SDK v5 is the Avalonia 12 compatibility baseline for LanMountainDesktop plugins.
## What Changed
- Rebuild plugins against `LanMountainDesktop.PluginSdk` `5.0.0`.
- Set `plugin.json` `apiVersion` to `5.0.0`.
- Target `net10.0` and use Avalonia `12.0.1` compatible UI dependencies.
- Use `FluentAvaloniaUI` `3.0.0-preview1` and `FluentIcons.Avalonia` `2.1.325` when a plugin directly references those packages.
## Compatibility
SDK v5 is a binary breaking change because the SDK exposes Avalonia UI types such as `Control`, `UserControl`, and `SettingsPageBase`. Plugins built for SDK v4 must be rebuilt and republished for SDK v5.
The host does not provide an Avalonia 11 / Avalonia 12 dual UI stack. The public extension entry points remain the same: custom settings pages still derive from `SettingsPageBase`, and desktop components still provide Avalonia controls through the existing registration APIs.
## Appearance Snapshot
`IPluginAppearanceContext.Snapshot` remains read-only. In addition to theme variant and corner radius tokens, the snapshot can now include host material/color data:
- `AccentColor`
- `SeedColor`
- `ColorSource`
- `SystemMaterialMode`
- `ColorRoles`
- `MaterialSurfaces`
- `WallpaperSeedCandidates`
Existing plugins that only read `CornerRadiusTokens` and `ThemeVariant` continue to work. New plugins should treat the added properties as optional and prefer `ColorRoles`/`MaterialSurfaces` over hard-coded colors.
## Minimal Package Update
```xml
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
</ItemGroup>
```
```json
{
"apiVersion": "5.0.0"
}
```
## Validation
After updating package versions and rebuilding the plugin, verify that the generated `.laapp` contains the rebuilt assembly, `plugin.json`, and `.deps.json` next to the plugin entry assembly.

62
docs/archive/PRODUCT.md Normal file
View File

@@ -0,0 +1,62 @@
# 产品文档 / Product
## 中文
### 产品一句话
阑山桌面——你的桌面,不止一面。
### 产品定位
- 产品类型:跨平台桌面环境增强工具
- 技术基线Avalonia UI + .NET 10
- 支持平台Windows、Linux、macOS
- 仓库角色本仓库是桌面宿主、插件运行时、Plugin SDK 与共享契约的权威来源
### 目标用户
- 学生用户:课程表、自习监测、计时、天气和日常信息聚合
- 办公用户:日历、资讯、最近文档、常用工具入口
- 效率和美化爱好者:自由布局、主题切换、插件扩展
- 中文用户:本地化界面、农历和节假日等本地语境支持
### 核心使用场景
- 学习辅助:课程表、自习环境监测、计时与知识卡片
- 信息聚合:天气、新闻、日历、热搜等信息集中展示
- 效率提升:最近文档、浏览器、工具组件与桌面快捷访问
- 个性化桌面:自由布局、多页桌面、主题与视觉风格配置
- 插件扩展:通过 `.laapp` 插件补充新的组件、设置页和集成功能
### 核心能力
- 桌面组件系统:内置组件与扩展组件统一注册、统一放置约束
- 插件系统:宿主加载插件、整合设置页、组件与市场安装流
- 外观系统:主题、玻璃层级、圆角与颜色资源统一管理
- 设置系统:独立设置窗口、设置页注册与分域持久化
- 跨平台运行:基于 Avalonia 的桌面宿主运行在 Windows、Linux、macOS
### 当前阶段
- 产品版本:`1.0.0`
- Plugin SDK API 基线:`5.0.0`
- 当前重点:持续完善宿主体验、设置页体验、组件能力与插件生态
- 近期需求入口:以 `.trae/specs/` 中的 feature spec 为准
### 生态边界
- 本仓库负责宿主代码、插件运行时、SDK、共享契约、主题与设置基础设施
- `LanAirApp` 负责:插件市场元数据、开发者生态材料
- `LanMountainDesktop.SamplePlugin` 负责:官方示例插件实现
### 维护原则
- 产品事实只在本文件沉淀,不在多个根目录文档重复维护
- 代码结构和运行方式分别以 `docs/ARCHITECTURE.md``docs/DEVELOPMENT.md` 为准
- 专题规范以 `docs/VISUAL_SPEC.md``docs/CORNER_RADIUS_SPEC.md` 等专题文档为准
## English
LanMountainDesktop is a cross-platform desktop enhancement product built with Avalonia UI and .NET 10. It targets students, office users, and customization-focused users who want a programmable desktop surface for information, tools, and plugin-driven extensions.
This repository is the source of truth for the desktop host, plugin runtime, Plugin SDK, shared contracts, and core appearance/settings infrastructure. The current product version is `1.0.0`, and the active Plugin SDK baseline in this repository is `5.0.0`.

View File

@@ -0,0 +1,118 @@
# 🧭 阑山桌面插件开发文档导航
欢迎来到 **LanMountainDesktop阑山桌面** 插件开发文档!
这套文档将帮助你从零开始,一步步掌握插件开发的完整流程,最终发布你的作品到插件市场。
---
## 📖 文档概述
**目标读者:**
- 有一定 .NET/C# 基础的开发者
- 熟悉或愿意学习 Avalonia UI 框架的开发者
- 想要为阑山桌面扩展功能的创意开发者
**你能学到什么:**
- 🚀 快速搭建插件开发环境
- 🧩 创建桌面组件Widgets
- ⚙️ 集成设置页面
- 🎨 适配主题和外观
- 🐛 调试和故障排除
- 🚀 CI/CD 自动化构建
- 📦 发布到插件市场
---
## 🛤️ 推荐阅读路径
### 🌱 新手路径(从零开始)
如果你从未开发过阑山桌面插件,请按以下顺序阅读:
1. **[01-开发环境准备](01-快速开始/01-开发环境准备.md)** - 安装必要工具和模板
2. **[02-三分钟创建第一个插件](01-快速开始/02-三分钟创建第一个插件.md)** - 快速上手,建立信心
3. **[03-插件项目结构详解](01-快速开始/03-插件项目结构详解.md)** - 理解项目组成
4. **[04-调试运行指南](01-快速开始/04-调试运行指南.md)** - 学会调试技巧
5. **[01-插件生命周期](02-核心概念与原理/01-插件生命周期.md)** - 理解运行原理
6. **[02-桌面组件系统](02-核心概念与原理/02-桌面组件系统.md)** - 创建你的第一个组件
7. **[01-开发天气组件](04-实战案例/01-开发天气组件.md)** - 完整实战案例
**预计时间:** 2-3 小时即可开发出第一个可用插件
### 🚀 有经验路径(已有 .NET/Avalonia 基础)
如果你已有相关经验,可以跳过基础部分:
1. **[01-开发环境准备](01-快速开始/01-开发环境准备.md)** - 快速配置环境
2. **[02-核心概念与原理/](02-核心概念与原理/)** - 了解阑山桌面的特殊机制
3. **[03-API实践指南/](03-API实践指南/)** - 查阅具体 API 用法
4. **[04-实战案例/](04-实战案例/)** - 参考完整示例
---
## 🔍 快速问题索引
| 我想知道... | 查看文档 |
|------------|---------|
| 如何搭建开发环境? | [01-开发环境准备](01-快速开始/01-开发环境准备.md) |
| 如何创建第一个插件? | [02-三分钟创建第一个插件](01-快速开始/02-三分钟创建第一个插件.md) |
| plugin.json 各字段含义? | [03-插件项目结构详解](01-快速开始/03-插件项目结构详解.md) |
| 如何调试插件代码? | [04-调试运行指南](01-快速开始/04-调试运行指南.md) |
| 插件什么时候初始化?能做什么? | [01-插件生命周期](02-核心概念与原理/01-插件生命周期.md) |
| 什么是桌面组件?如何创建? | [02-桌面组件系统](02-核心概念与原理/02-桌面组件系统.md) |
| 如何添加设置页面? | [03-设置系统集成](02-核心概念与原理/03-设置系统集成.md) + [04-开发设置页面](04-实战案例/04-开发设置页面.md) |
| 如何适配暗色模式? | [04-外观与主题系统](02-核心概念与原理/04-外观与主题系统.md) |
| 插件之间如何通信? | [05-插件间通信](02-核心概念与原理/05-插件间通信.md) |
| 完整的组件开发示例? | [01-开发天气组件](04-实战案例/01-开发天气组件.md) |
| 如何排查插件不加载的问题? | [03-常见问题排查](05-调试与故障排除/03-常见问题排查.md) |
| 如何配置 GitHub Actions | [01-GitHub Actions入门](06-CI-CD与自动化/01-GitHub Actions入门.md) |
| 如何自动打包 .laapp | [03-自动打包与发布](06-CI-CD与自动化/03-自动打包与发布.md) |
| 如何发布到插件市场? | [03-发布到插件市场](07-发布与运营/03-发布到插件市场.md) |
---
## 📚 相关资源
### 官方资源
| 资源 | 位置 | 说明 |
|-----|------|------|
| **Plugin SDK 源码** | `LanMountainDesktop.PluginSdk/` | SDK 的完整源码和 XML 注释 |
| **插件模板** | `LanMountainDesktop.PluginTemplate/` | `dotnet new` 模板源码 |
| **共享契约** | `LanMountainDesktop.Shared.Contracts/` | 宿主与插件共享的类型定义 |
| **架构文档** | `docs/ARCHITECTURE.md` | 宿主应用架构说明 |
| **视觉规范** | `docs/VISUAL_SPEC.md` | UI 设计规范 |
| **圆角规范** | `docs/CORNER_RADIUS_SPEC.md` | 圆角设计系统 |
| **开发指南** | `docs/DEVELOPMENT.md` | 宿主开发指南 |
### 外部资源
| 资源 | 链接 | 说明 |
|-----|------|------|
| **示例插件仓库** | `LanMountainDesktop.SamplePlugin` | 官方示例插件(独立仓库) |
| **Avalonia UI 文档** | https://docs.avaloniaui.net/ | UI 框架官方文档 |
| **FluentAvalonia** | https://github.com/amwx/FluentAvalonia | 主题控件库 |
| **.NET 文档** | https://learn.microsoft.com/dotnet/ | .NET 官方文档 |
---
## 💡 获取帮助
如果在开发过程中遇到问题:
1. **查阅本文档** - 使用上方快速索引找到相关章节
2. **查看示例代码** - 参考 `LanMountainDesktop.PluginTemplate/content/` 中的模板代码
3. **阅读 SDK 源码** - `LanMountainDesktop.PluginSdk/` 中有详细的 XML 注释
4. **搜索 Issues** - 在 GitHub 仓库搜索是否有人遇到类似问题
5. **提交 Issue** - 如果确认是 bug欢迎提交 Issue
---
## 🎯 下一步
准备好开始了吗?点击 **[01-开发环境准备](01-快速开始/01-开发环境准备.md)** 开始你的插件开发之旅!
---
*最后更新2026年4月*

View File

@@ -0,0 +1,220 @@
# 01-开发环境准备
在开始开发阑山桌面插件之前,你需要准备好开发环境。本指南将带你完成所有必要的安装和配置。
---
## ✅ 系统要求
### 支持的操作系统
| 操作系统 | 版本要求 | 备注 |
|---------|---------|------|
| **Windows** | Windows 10 版本 1809 或更高 | 推荐开发平台 |
| **Windows** | Windows 11 | 最佳体验 |
| **Linux** | Ubuntu 20.04+ / Debian 10+ | 支持开发和运行 |
| **macOS** | macOS 12+ | 支持开发和运行 |
### 硬件要求
- **处理器**x64 或 ARM64 架构
- **内存**:至少 4GB RAM推荐 8GB
- **磁盘空间**:至少 2GB 可用空间
---
## 🛠️ 安装 .NET SDK
阑山桌面插件基于 **.NET 10** 开发,你需要安装对应版本的 SDK。
### 下载安装
1. 访问 [.NET 10 下载页面](https://dotnet.microsoft.com/download/dotnet/10.0)
2. 下载适合你操作系统的 SDK 安装包
3. 运行安装程序,按提示完成安装
### 验证安装
打开终端PowerShell、CMD 或 Bash运行以下命令
```powershell
# 检查 .NET SDK 版本
dotnet --version
```
**预期输出示例:**
```
10.0.100
```
⚠️ **如果版本低于 10.0**,请重新下载安装最新版 .NET 10 SDK。
---
## 💻 安装 IDE集成开发环境
你可以选择以下任一 IDE 进行开发:
### 选项 1Visual Studio 2022推荐 Windows 用户)
**优点:** 功能最全,调试体验最佳
1. 下载 [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
2. 安装时选择以下工作负载:
-**.NET 桌面开发**
-**Avalonia UI 开发**(可选,如需 Avalonia 设计器)
### 选项 2JetBrains Rider跨平台推荐
**优点:** 跨平台智能提示强大Avalonia 支持好
1. 下载 [Rider](https://www.jetbrains.com/rider/)
2. 安装后打开,会自动检测 .NET SDK
### 选项 3Visual Studio Code轻量级
**优点:** 免费,轻量,插件丰富
1. 下载 [VS Code](https://code.visualstudio.com/)
2. 安装以下扩展:
- **C# Dev Kit**Microsoft 官方)
- **Avalonia for VS Code**(可选)
---
## 📦 安装插件模板
阑山桌面提供了官方的 `dotnet new` 模板,帮助你快速创建插件项目。
### 安装模板
```powershell
# 安装最新版插件模板
dotnet new install LanMountainDesktop.PluginTemplate
```
**成功提示:**
```
模板名 短名称 语言 标签
------------------------------------- ---------- ---- ------------
LanMountainDesktop Plugin lmd-plugin C# LanMountainDesktop/Plugin
```
### 验证安装
```powershell
# 列出已安装的模板,查找 lmd-plugin
dotnet new list | findstr lmd
```
Linux/macOS
```bash
dotnet new list | grep lmd
```
---
## 🎮 获取宿主应用
插件需要在阑山桌面宿主中运行,你需要获取宿主应用:
### 方式 1下载 Release 版本(推荐)
1. 访问 GitHub Releases 页面
2. 下载最新版本的安装包(.exe / .deb / .dmg
3. 安装并运行阑山桌面
### 方式 2从源码构建
如果你想调试宿主或了解内部机制:
```powershell
# 克隆仓库
git clone https://github.com/your-org/LanMountainDesktop.git
cd LanMountainDesktop
# 还原依赖
dotnet restore
# 构建项目
dotnet build LanMountainDesktop.slnx -c Debug
# 运行宿主
dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj
```
---
## 🔍 环境验证清单
在继续之前,请确认以下检查项都已完成:
| 检查项 | 验证命令 | 预期结果 |
|-------|---------|---------|
| ✅ .NET SDK 版本 | `dotnet --version` | 10.0.xxx |
| ✅ 模板已安装 | `dotnet new list \| findstr lmd` | 显示 lmd-plugin |
| ✅ IDE 可创建项目 | 在 IDE 中新建项目 | 能看到 C# 项目模板 |
| ✅ 宿主可运行 | 双击 LanMountainDesktop.exe | 应用正常启动 |
---
## ⚠️ 常见问题
### 问题 1dotnet 命令找不到
**现象:** 运行 `dotnet` 提示不是内部或外部命令
**解决:**
1. 确认 .NET SDK 已正确安装
2. 重启终端或 IDE
3. 检查环境变量 PATH 是否包含 `C:\Program Files\dotnet\`
### 问题 2模板安装失败
**现象:** `dotnet new install` 报错或卡住
**解决:**
1. 检查网络连接(需要访问 nuget.org
2. 尝试指定版本号:
```powershell
dotnet new install LanMountainDesktop.PluginTemplate::1.0.0
```
3. 清除模板缓存后重试:
```powershell
dotnet new uninstall LanMountainDesktop.PluginTemplate
dotnet new install LanMountainDesktop.PluginTemplate
```
### 问题 3SDK 版本不匹配
**现象:** 构建时提示 SDK 版本不符合 global.json 要求
**解决:**
1. 检查项目根目录的 `global.json` 文件
2. 安装对应版本的 .NET SDK
3. 或使用以下命令使用已安装的版本:
```powershell
dotnet new globaljson --sdk-version 10.0.100 --roll-forward latestFeature
```
---
## 🎯 下一步
环境准备完成!接下来:
👉 **[02-三分钟创建第一个插件](02-三分钟创建第一个插件.md)** - 立即开始创建你的第一个插件!
---
## 📚 参考资源
- [.NET 10 下载](https://dotnet.microsoft.com/download/dotnet/10.0)
- [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
- [JetBrains Rider](https://www.jetbrains.com/rider/)
- [VS Code](https://code.visualstudio.com/)
- [Avalonia UI 文档](https://docs.avaloniaui.net/)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,236 @@
# 02-三分钟创建第一个插件
本指南将帮助你在三分钟内创建并运行你的第一个阑山桌面插件。让我们开始吧!
---
## 🎯 目标
完成本指南后,你将:
- ✅ 创建一个可运行的插件项目
- ✅ 在宿主中成功加载插件
- ✅ 在插件列表中看到你的插件
---
## ⚡ 步骤一创建项目30秒
打开终端,运行以下命令:
```powershell
# 创建插件项目
dotnet new lmd-plugin -n MyFirstPlugin
# 进入项目目录
cd MyFirstPlugin
```
**成功标志:** 命令执行后没有报错,且生成了 `MyFirstPlugin` 文件夹。
---
## 📝 步骤二配置插件信息30秒
打开 `plugin.json` 文件,修改以下字段:
```json
{
"id": "com.yourname.myfirstplugin",
"name": "我的第一个插件",
"description": "这是一个测试插件",
"author": "你的名字",
"version": "1.0.0",
"apiVersion": "5.0.0",
"entranceAssembly": "MyFirstPlugin.dll",
"sharedContracts": []
}
```
⚠️ **重要提示:**
- `id` 必须是唯一的,建议使用反向域名格式(如 `com.yourname.pluginname`
- `apiVersion` 必须与 SDK 版本匹配
- 保存文件时使用 **UTF-8** 编码
---
## 🔨 步骤三构建项目30秒
在终端中运行:
```powershell
# 构建项目
dotnet build
```
**成功标志:** 看到类似以下的输出:
```
生成成功。
0 个警告
0 个错误
```
---
## 📦 步骤四找到插件包15秒
构建完成后,插件包位于:
```
MyFirstPlugin/
└── bin/
└── Debug/
└── net10.0/
└── MyFirstPlugin.laapp <-- 这就是插件包!
```
⚠️ **什么是 .laapp 文件?**
- `.laapp` 是阑山桌面的插件包格式
- 本质上是一个 ZIP 压缩包,包含插件 DLL 和资源文件
- 不要解压,直接安装即可
---
## 🚀 步骤五安装到宿主30秒
1. **启动阑山桌面**(如果尚未运行)
2. **打开设置**
- 右键点击桌面上的阑山桌面图标
- 选择「设置」
3. **进入插件管理**
- 在设置窗口左侧选择「插件」
4. **安装本地插件**
- 点击「安装本地插件」按钮
- 选择刚才生成的 `.laapp` 文件
- 点击「打开」
5. **重启宿主**
- 安装完成后,点击「重启」按钮
- 阑山桌面将重新启动
---
## ✅ 步骤六验证安装15秒
重启后,再次打开设置 → 插件:
🎉 **成功标志:**
- 在插件列表中看到「我的第一个插件」
- 状态显示为「已启用」
- 作者显示为你设置的名字
![插件列表示意图](此处应有截图位置)
---
## 📂 生成的项目结构
你的项目现在包含以下文件:
```
MyFirstPlugin/
├── plugin.json # 插件清单文件
├── MyFirstPlugin.csproj # 项目文件
├── Plugin.cs # 插件入口类
├── README.md # 项目说明
└── Localization/ # 本地化文件夹
├── zh-CN.json # 中文资源
└── en-US.json # 英文资源
```
---
## 🔍 查看插件代码
打开 `Plugin.cs`,你会看到:
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyFirstPlugin;
[PluginEntrance]
public sealed class Plugin : PluginBase
{
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 插件初始化代码
// 在这里注册组件、设置页面等
}
}
```
**关键点:**
- `[PluginEntrance]` 特性标记入口类
- 继承 `PluginBase` 基类
- `Initialize` 方法是插件的初始化入口
---
## 🎉 恭喜!
你已经成功创建并安装了第一个阑山桌面插件!
虽然这个插件目前还没有任何功能,但你已经掌握了:
- ✅ 使用模板创建项目
- ✅ 配置插件信息
- ✅ 构建插件包
- ✅ 安装到宿主
---
## 🚦 常见问题
### 问题 1构建失败提示找不到 SDK
**现象:** 错误信息包含 "SDK not found"
**解决:**
1. 确认已安装 .NET 10 SDK`dotnet --version`
2. 检查 `global.json` 中的版本要求
### 问题 2宿主提示插件安装失败
**现象:** 安装时弹出错误对话框
**排查步骤:**
1. 检查 `plugin.json` 是否为有效的 JSON 格式
2. 确认 `id` 字段唯一且合法(只能包含字母、数字、点号)
3. 确认 `apiVersion` 与 SDK 版本匹配
### 问题 3插件列表中不显示
**现象:** 安装后重启,但列表中没有
**排查步骤:**
1. 确认已点击「重启」按钮
2. 检查日志文件:`%LOCALAPPDATA%\LanMountainDesktop\logs\`
3. 确认 `.laapp` 文件完整未损坏
---
## 🎯 下一步
现在你的插件已经能运行了,接下来学习:
👉 **[03-插件项目结构详解](03-插件项目结构详解.md)** - 深入理解每个文件的作用
或者直接进入实战:
👉 **[02-桌面组件系统](../02-核心概念与原理/02-桌面组件系统.md)** - 创建你的第一个桌面组件!
---
## 💡 小贴士
- **快速重建**:修改代码后,只需运行 `dotnet build` 即可重新生成 `.laapp`
- **自动安装**:可以在 IDE 中配置构建后自动复制到宿主插件目录
- **日志调试**:使用 `ILogger` 记录日志,在 `%LOCALAPPDATA%\LanMountainDesktop\logs\` 查看
---
*最后更新2026年4月*

View File

@@ -0,0 +1,350 @@
# 03-插件项目结构详解
了解插件项目的每个文件和文件夹的作用,是开发高质量插件的基础。本文将详细解析插件项目的完整结构。
---
## 📂 项目结构概览
使用模板创建的插件项目结构如下:
```
MyPlugin/
├── plugin.json # 插件清单(必需)
├── MyPlugin.csproj # 项目文件(必需)
├── Plugin.cs # 入口类(必需)
├── README.md # 项目说明(推荐)
├── .gitignore # Git忽略文件可选
└── Localization/ # 本地化文件夹(可选)
├── zh-CN.json # 中文资源
└── en-US.json # 英文资源
```
---
## 📋 plugin.json - 插件清单
这是插件最重要的配置文件,定义了插件的元数据。
### 完整示例
```json
{
"id": "com.example.myplugin",
"name": "我的插件",
"description": "这是一个示例插件",
"author": "作者名称",
"version": "1.0.0",
"apiVersion": "5.0.0",
"entranceAssembly": "MyPlugin.dll",
"sharedContracts": [],
"website": "https://example.com",
"icon": "icon.png",
"tags": ["工具", "实用"]
}
```
### 字段详解
| 字段 | 必需 | 说明 | 示例 |
|-----|------|------|------|
| `id` | ✅ | 唯一标识符,反向域名格式 | `com.yourname.plugin` |
| `name` | ✅ | 显示名称 | `天气插件` |
| `description` | ✅ | 简短描述 | `显示实时天气信息` |
| `author` | ✅ | 作者名称 | `张三` |
| `version` | ✅ | 版本号(语义化版本) | `1.0.0` |
| `apiVersion` | ✅ | SDK API 版本 | `5.0.0` |
| `entranceAssembly` | ✅ | 入口程序集文件名 | `MyPlugin.dll` |
| `sharedContracts` | ✅ | 共享契约类型列表 | `[]` |
| `website` | ❌ | 项目网站 | `https://github.com/...` |
| `icon` | ❌ | 图标文件名 | `icon.png` |
| `tags` | ❌ | 标签数组 | `["天气", "工具"]` |
### 重要规则
⚠️ **id 字段规则:**
- 只能包含小写字母、数字、点号(`.`
- 必须全局唯一
- 建议使用反向域名格式:`com.yourname.pluginname`
- 一经发布不可更改
⚠️ **version 字段规则:**
- 使用语义化版本格式:`主版本.次版本.修订号`
- 示例:`1.0.0``2.1.3-beta`
⚠️ **apiVersion 字段规则:**
- 必须与引用的 SDK 版本兼容
- 当前最新版本:`5.0.0`
- 不兼容时宿主将拒绝加载插件
---
## 🔧 .csproj - 项目文件
定义了项目的构建配置和依赖项。
### 完整示例
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Localization\**\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
```
### 关键配置项
| 配置项 | 说明 | 推荐值 |
|-------|------|--------|
| `TargetFramework` | 目标框架 | `net10.0` |
| `LangVersion` | C# 语言版本 | `latest` |
| `Nullable` | 可空引用类型 | `enable` |
### SDK 引用
```xml
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
```
⚠️ **版本必须匹配:**
- SDK 版本必须与 `plugin.json` 中的 `apiVersion` 兼容
- 建议使用最新稳定版
### 资源文件配置
确保 `plugin.json` 和本地化文件被正确复制到输出目录:
```xml
<ItemGroup>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Localization\**\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
```
---
## 🚪 Plugin.cs - 入口类
插件的入口点,负责初始化逻辑。
### 基本结构
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyPlugin;
[PluginEntrance]
public sealed class Plugin : PluginBase
{
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 在这里注册组件、设置页面、服务等
// 示例:注册桌面组件
// services.AddPluginDesktopComponent<MyWidget>(...);
// 示例:注册设置页面
// services.AddPluginSettingsSection(...);
}
}
```
### 关键特性
| 特性/类 | 说明 |
|--------|------|
| `[PluginEntrance]` | 标记插件入口类,必须有且仅有一个 |
| `PluginBase` | 插件基类,提供基础功能和日志访问 |
| `Initialize` | 初始化方法,宿主启动时调用 |
### Initialize 方法参数
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
```
| 参数 | 类型 | 说明 |
|-----|------|------|
| `context` | `HostBuilderContext` | 宿主构建上下文,可访问配置 |
| `services` | `IServiceCollection` | 依赖注入服务集合,用于注册组件和服务 |
---
## 🌍 Localization - 本地化文件夹
存放多语言资源文件,支持插件的国际化。
### 文件夹结构
```
Localization/
├── zh-CN.json # 简体中文
├── zh-TW.json # 繁体中文
├── en-US.json # 英文(美国)
├── ja-JP.json # 日文
└── ko-KR.json # 韩文
```
### 资源文件格式
```json
{
"PluginName": "我的插件",
"Settings": {
"Title": "设置",
"RefreshInterval": "刷新间隔"
},
"Messages": {
"Loading": "加载中...",
"Error": "出错了:{0}"
}
}
```
### 在代码中使用
```csharp
// 获取本地化字符串
var localizer = serviceProvider.GetRequiredService<IStringLocalizer<MyPlugin>>();
var pluginName = localizer["PluginName"];
var message = localizer["Messages.Error", errorDetails];
```
### 支持的语言代码
| 语言 | 代码 |
|-----|------|
| 简体中文 | `zh-CN` |
| 繁体中文 | `zh-TW` |
| 英文 | `en-US` |
| 日文 | `ja-JP` |
| 韩文 | `ko-KR` |
---
## 📦 构建输出结构
运行 `dotnet build` 后,生成的输出结构:
```
bin/Debug/net10.0/
├── MyPlugin.dll # 插件程序集
├── MyPlugin.pdb # 调试符号
├── plugin.json # 插件清单(复制)
├── Localization/ # 本地化文件夹(复制)
│ └── zh-CN.json
├── MyPlugin.laapp # 插件包(由 SDK 自动生成)
└── ...(依赖项 DLL
```
### .laapp 包结构
`.laapp` 文件本质是一个 ZIP 压缩包,包含:
```
MyPlugin.laapp
├── plugin.json # 清单文件
├── MyPlugin.dll # 主程序集
├── Localization/ # 本地化资源
└── ...(其他依赖 DLL
```
---
## 🔗 与其他 .NET 项目的区别
| 特性 | 普通 .NET 应用 | 阑山桌面插件 |
|-----|---------------|-------------|
| 入口点 | `Program.cs``Main` | `Plugin.cs``Initialize` |
| 运行方式 | 独立运行 | 由宿主加载运行 |
| 依赖注入 | 自行配置 | 使用宿主提供的 `IServiceCollection` |
| 输出格式 | `.exe``.dll` | `.laapp` 包 |
| 资源访问 | 直接访问 | 通过 SDK API 访问宿主资源 |
| 热重载 | 支持 | 不支持(需重启宿主) |
---
## 🎯 最佳实践
### 项目组织建议
```
MyPlugin/
├── plugin.json
├── MyPlugin.csproj
├── Plugin.cs # 入口类(保持简洁)
├── README.md
├── .gitignore
├── Localization/ # 本地化资源
├── Services/ # 服务类文件夹
│ ├── WeatherService.cs
│ └── DataService.cs
├── Views/ # 视图文件夹
│ ├── WeatherWidget.axaml
│ ├── WeatherWidget.axaml.cs
│ └── SettingsPage.axaml
└── ViewModels/ # 视图模型文件夹
├── WeatherViewModel.cs
└── SettingsViewModel.cs
```
### 文件命名规范
| 类型 | 命名约定 | 示例 |
|-----|---------|------|
| 入口类 | `Plugin` | `Plugin.cs` |
| 组件视图 | `{Name}Widget` | `WeatherWidget.axaml` |
| 设置页面 | `{Name}SettingsPage` | `WeatherSettingsPage.axaml` |
| 服务类 | `{Name}Service` | `WeatherService.cs` |
| 视图模型 | `{Name}ViewModel` | `WeatherViewModel.cs` |
---
## 📚 参考资源
- [Plugin SDK 源码](../../LanMountainDesktop.PluginSdk/)
- [插件模板](../../LanMountainDesktop.PluginTemplate/content/)
- [02-桌面组件系统](../02-核心概念与原理/02-桌面组件系统.md)
- [03-设置系统集成](../02-核心概念与原理/03-设置系统集成.md)
---
## 🎯 下一步
理解了项目结构后,接下来学习:
👉 **[04-调试运行指南](04-调试运行指南.md)** - 掌握调试技巧
或者深入了解核心概念:
👉 **[01-插件生命周期](../02-核心概念与原理/01-插件生命周期.md)** - 理解插件运行机制
---
*最后更新2026年4月*

View File

@@ -0,0 +1,380 @@
# 04-调试运行指南
掌握插件调试技巧,能大幅提升开发效率。本文介绍阑山桌面插件的各种调试方法和常见问题排查。
---
## 🔄 调试方式概述
阑山桌面插件有两种主要调试方式:
| 方式 | 适用场景 | 优点 | 缺点 |
|-----|---------|------|------|
| **附加到进程** | 日常开发调试 | 不改变项目结构 | 每次需手动附加 |
| **独立调试** | 深度调试、单元测试 | 启动即调试 | 配置较复杂 |
---
## 🎯 方式一:附加到进程(推荐)
这是日常开发中最常用的调试方式。
### 步骤
1. **启动阑山桌面**
- 正常启动宿主应用(非调试模式)
- 确保你的插件已安装
2. **在 IDE 中打开插件项目**
- 使用 Visual Studio / Rider / VS Code 打开项目
3. **设置断点**
- 在你想要调试的代码行左侧点击,设置断点
- 常见断点位置:
- `Plugin.Initialize()` - 插件初始化
- 组件构造函数
- 设置页面加载方法
4. **附加到进程**
**Visual Studio**
- 菜单:`调试``附加到进程`
- 或快捷键:`Ctrl+Alt+P`
- 在列表中找到 `LanMountainDesktop.exe`
- 点击`附加`
**Rider**
- 菜单:`Run``Attach to Process`
- 或快捷键:`Ctrl+Alt+F5`
- 选择 `LanMountainDesktop.exe`
**VS Code**
-`Ctrl+Shift+D` 打开调试面板
- 点击`创建 launch.json 文件`
- 选择 `.NET Core Attach`
- 选择 `LanMountainDesktop` 进程
5. **触发调试**
- 在阑山桌面中操作,触发插件代码
- 例如:添加组件、打开设置页面等
- 程序会在断点处暂停
### 附加配置VS Code
创建 `.vscode/launch.json`
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "附加到阑山桌面",
"type": "coreclr",
"request": "attach",
"processName": "LanMountainDesktop"
}
]
}
```
---
## 🔧 方式二:独立调试
适用于深度调试或单元测试。
### 配置步骤
1. **修改 .csproj 临时引用宿主**
```xml
<ItemGroup>
<!-- 临时添加,仅用于调试 -->
<ProjectReference Include="..\LanMountainDesktop\LanMountainDesktop.csproj" />
</ItemGroup>
```
2. **创建调试启动配置**
**Visual Studio**
- 右键项目 → `属性` → `调试`
- 启动外部程序:选择 `LanMountainDesktop.exe`
- 工作目录:设为宿主输出目录
**VS Code launch.json**
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "启动阑山桌面(调试)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/../LanMountainDesktop/bin/Debug/net10.0/LanMountainDesktop.exe",
"args": [],
"cwd": "${workspaceFolder}/../LanMountainDesktop/bin/Debug/net10.0",
"stopAtEntry": false
}
]
}
```
3. **启动调试**
- 按 `F5` 启动
- 宿主会以调试模式启动
- 插件代码中的断点会直接命中
⚠️ **注意:** 发布插件前务必移除临时引用!
---
## 📝 日志调试
当断点调试不方便时,日志是最有效的调试手段。
### 使用 ILogger
```csharp
using Microsoft.Extensions.Logging;
public class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("开始执行任务");
try
{
// 业务逻辑
_logger.LogDebug("处理数据: {Data}", data);
}
catch (Exception ex)
{
_logger.LogError(ex, "任务执行失败");
}
_logger.LogInformation("任务完成");
}
}
```
### 日志级别
| 级别 | 使用场景 |
|-----|---------|
| `LogTrace` | 最详细的跟踪信息 |
| `LogDebug` | 调试信息 |
| `LogInformation` | 一般信息 |
| `LogWarning` | 警告信息 |
| `LogError` | 错误信息 |
| `LogCritical` | 严重错误 |
### 查看日志文件
日志文件位置:
```
Windows: %LOCALAPPDATA%\LanMountainDesktop\logs\
Linux: ~/.local/share/LanMountainDesktop/logs/
macOS: ~/Library/Application Support/LanMountainDesktop/logs/
```
日志文件命名格式:
```
log-20240413.txt
log-20240413_001.txt
```
### 实时查看日志
**Windows PowerShell**
```powershell
Get-Content "$env:LOCALAPPDATA\LanMountainDesktop\logs\log-$(Get-Date -Format 'yyyyMMdd').txt" -Wait
```
**Linux/macOS**
```bash
tail -f ~/.local/share/LanMountainDesktop/logs/log-$(date +%Y%m%d).txt
```
---
## 🚫 热重载限制
⚠️ **重要:** 阑山桌面插件**不支持**热重载Hot Reload
### 原因
插件运行在独立的 `AssemblyLoadContext` 中,.NET 不支持卸载已加载的程序集。因此:
- 修改代码后必须重新构建
- 必须重启宿主才能加载新版本
- 无法使用 `dotnet watch`
### 高效开发流程
```
修改代码 → dotnet build → 重启宿主 → 测试
```
**加速技巧:**
1. **创建批处理脚本**`rebuild-and-run.ps1`
```powershell
dotnet build
Stop-Process -Name "LanMountainDesktop" -ErrorAction SilentlyContinue
Start-Process "C:\Path\To\LanMountainDesktop.exe"
```
2. **使用 Rider 的外部工具**
- 配置构建后自动复制 `.laapp` 到插件目录
---
## 🐛 常见问题排查
### 问题 1断点不命中
**可能原因:**
- 插件未重新构建
- PDB 符号文件未生成
- 附加到了错误的进程
**解决步骤:**
1. 确认已重新构建:`dotnet build`
2. 检查输出目录是否有 `.pdb` 文件
3. 确认附加的是 `LanMountainDesktop.exe`(不是 `LanMountainDesktop.dll`
4. 尝试清理重建:
```powershell
dotnet clean
dotnet build
```
### 问题 2插件不加载
**排查步骤:**
1. **检查日志**
```powershell
Get-Content "$env:LOCALAPPDATA\LanMountainDesktop\logs\log-$(Get-Date -Format 'yyyyMMdd').txt" | Select-String "MyPlugin"
```
2. **验证 plugin.json**
- JSON 格式是否有效
- `id` 是否合法(只含小写字母、数字、点号)
- `apiVersion` 是否与 SDK 版本匹配
3. **检查 .laapp 包**
- 用压缩软件打开,确认文件完整
- 确认 `plugin.json` 和 DLL 存在
### 问题 3依赖项找不到
**现象:** `FileNotFoundException` 或 `Could not load file or assembly`
**解决:**
1. 确保所有依赖项都复制到输出目录
2. 在 `.csproj` 中添加:
```xml
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
```
### 问题 4调试时宿主卡顿
**原因:** 断点暂停导致 UI 线程阻塞
**解决:**
- 使用 `Debugger.Break()` 代替断点
- 或使用日志代替断点调试
---
## 💡 调试技巧
### 1. 条件断点
当需要在特定条件下暂停时使用:
**Visual Studio**
- 右键断点 → `条件`
- 输入条件表达式,如:`count > 10`
### 2. 日志点Tracepoint
不暂停程序,只输出日志:
**Visual Studio**
- 右键断点 → `操作`
- 勾选 `将消息输出到输出窗口`
- 输入消息模板:`变量值: {variableName}`
### 3. 异常设置
自动在抛出异常时中断:
**Visual Studio**
- `调试` → `窗口` → `异常设置`
- 勾选 `Common Language Runtime Exceptions`
### 4. 立即窗口
在调试时执行代码:
**Visual Studio**
- 快捷键:`Ctrl+Alt+I`
- 可查看变量值、调用方法
---
## 📊 性能调试
### 使用 Diagnostic Tools
**Visual Studio**
- 调试时自动显示 CPU 和内存使用情况
- `调试` → `窗口` → `诊断工具`
### 内存泄漏排查
```csharp
// 在可疑位置添加诊断代码
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memory = GC.GetTotalMemory(true);
Debug.WriteLine($"内存使用: {memory / 1024 / 1024} MB");
```
---
## 🎯 下一步
掌握了调试技巧后,接下来学习核心概念:
👉 **[01-插件生命周期](../02-核心概念与原理/01-插件生命周期.md)** - 理解插件运行机制
或者查看实战案例:
👉 **[01-开发天气组件](../04-实战案例/01-开发天气组件.md)** - 完整开发流程
---
## 📚 参考资源
- [PluginBase 源码](../../LanMountainDesktop.PluginSdk/PluginBase.cs)
- [docs/DEVELOPMENT.md](../../docs/DEVELOPMENT.md)
- [Visual Studio 调试文档](https://docs.microsoft.com/visualstudio/debugger/)
- [Rider 调试文档](https://www.jetbrains.com/help/rider/Debugging.html)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,398 @@
# 01-插件生命周期
理解插件的生命周期,是开发稳定可靠插件的基础。本文详细讲解插件从加载到卸载的完整过程。
---
## 🔄 生命周期概览
```
┌─────────────────────────────────────────────────────────────┐
│ 阑山桌面启动 │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 1. 发现插件 │
│ - 扫描插件目录 │
│ - 解析 plugin.json │
│ - 验证 API 版本兼容性 │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 2. 加载插件 │
│ - 创建 AssemblyLoadContext │
│ - 加载插件 DLL │
│ - 查找入口类(带 [PluginEntrance] 特性) │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 3. 初始化Initialize
│ - 调用 Plugin.Initialize() │
│ - 注册组件、设置页面、服务 │
│ - ⚠️ 此时 UI 尚未完全就绪 │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 4. 运行中 │
│ - 组件被添加到桌面 │
│ - 用户与组件交互 │
│ - 设置页面被打开 │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 5. 停用/卸载 │
│ - 用户禁用插件 │
│ - 或关闭阑山桌面 │
│ - 释放资源(当前版本无显式卸载回调) │
└─────────────────────────────────────────────────────────────┘
```
---
## 📋 各阶段详解
### 阶段 1发现插件
**时机:** 阑山桌面启动时
**过程:**
1. 扫描 `%LOCALAPPDATA%\LanMountainDesktop\plugins\` 目录
2. 读取每个 `.laapp` 包中的 `plugin.json`
3. 验证 `apiVersion` 是否与宿主兼容
4. 检查 `id` 是否唯一
**可能失败的原因:**
- `plugin.json` 格式错误
- `apiVersion` 不兼容
- `id` 与其他插件冲突
---
### 阶段 2加载插件
**时机:** 发现成功后
**过程:**
1. 创建独立的 `AssemblyLoadContext`
2. 加载插件 DLL 及其依赖项
3. 查找带有 `[PluginEntrance]` 特性的类
4. 实例化插件入口类
**代码示例:**
```csharp
[PluginEntrance] // ← 这个特性标记入口类
public sealed class Plugin : PluginBase
{
// 插件实例在此阶段被创建
}
```
⚠️ **重要:** 此阶段**不要**执行耗时操作,只应进行简单的字段初始化。
---
### 阶段 3初始化Initialize
**时机:** 插件加载完成后
**这是插件开发中最重要的阶段!**
#### 方法签名
```csharp
public override void Initialize(
HostBuilderContext context, // 宿主构建上下文
IServiceCollection services) // 服务注册集合
```
#### 可执行的操作
**可以做的:**
- 注册桌面组件
- 注册设置页面
- 注册服务到依赖注入容器
- 读取配置
- 初始化资源
**不应该做的:**
- 访问 UI 元素UI 尚未就绪)
- 执行耗时阻塞操作
- 创建窗口或对话框
#### 典型初始化代码
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 1. 注册服务
services.AddSingleton<IWeatherService, WeatherService>();
// 2. 注册桌面组件
services.AddPluginDesktopComponent<WeatherWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "MyPlugin.Weather",
DisplayName = "天气",
IconKey = "Weather",
Category = "工具",
MinWidthCells = 4,
MinHeightCells = 3
});
// 3. 注册设置页面
services.AddPluginSettingsSection(
"myplugin-settings",
"天气设置",
section => section
.AddToggle("auto_refresh", "自动刷新", defaultValue: true)
.AddNumber("interval", "刷新间隔(分钟)", defaultValue: 30),
iconKey: "Settings");
}
```
---
### 阶段 4运行中
**时机:** 初始化完成后,直到插件被禁用或宿主关闭
**特点:**
- 组件可以被添加到桌面
- 用户可以与组件交互
- 设置页面可以被打开
- 定时器可以运行
#### 组件生命周期
```
用户添加组件
┌─────────────────┐
│ 创建组件实例 │ ← 调用构造函数
│ (Dependency │ 注入 IServiceProvider
│ Injection) │
└────────┬────────┘
┌─────────────────┐
│ 组件初始化 │ ← 可在此时加载数据
│ (Loaded事件) │
└────────┬────────┘
┌─────────────────┐
│ 渲染显示 │ ← 用户看到组件
└────────┬────────┘
┌────┴────┐
▼ ▼
用户交互 定时更新
│ │
└────┬────┘
┌─────────────────┐
│ 组件移除 │ ← 用户删除组件
│ (Unloaded事件) │ 或关闭宿主
└─────────────────┘
```
---
### 阶段 5停用/卸载
**时机:**
- 用户在设置中禁用插件
- 卸载插件
- 关闭阑山桌面
**当前限制:**
- SDK v5 暂无显式的卸载回调方法
- 资源释放依赖 .NET 垃圾回收
- 建议:
- 使用 `IDisposable` 模式管理资源
- 在组件卸载事件中清理资源
---
## ⏱️ 启动时序图
```
阑山桌面 插件系统 你的插件
│ │ │
│── 启动 ───────►│ │
│ │ │
│ │── 发现插件 ───►│
│ │ │ (读取 plugin.json)
│ │◄───────────────│
│ │ │
│ │── 加载 DLL ───►│
│ │ │ (AssemblyLoadContext)
│ │◄───────────────│
│ │ │
│ │── 创建实例 ───►│
│ │ │ (调用构造函数)
│ │◄───────────────│
│ │ │
│ │── Initialize ─►│
│ │ │ (注册组件/服务)
│ │◄───────────────│
│ │ │
│◄───────────────│ │
│ │ │
│── UI就绪 ─────►│ │
│ │ │
│ │── 用户添加组件 ─►│
│ │ │ (创建组件实例)
```
---
## 💡 最佳实践
### 1. Initialize 方法保持轻量
```csharp
// ✅ 好的做法:快速注册
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddSingleton<IDataService, DataService>();
services.AddPluginDesktopComponent<MyWidget>(options);
}
// ❌ 避免:耗时操作
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 不要这样做!
var data = FetchDataFromInternet().Result; // 阻塞!
services.AddSingleton(data);
}
```
### 2. 延迟加载数据
```csharp
public class MyWidget : Border
{
private readonly IDataService _dataService;
public MyWidget(PluginDesktopComponentContext context)
{
_dataService = context.ServiceProvider.GetRequiredService<IDataService>();
// 在 Loaded 事件中加载数据,而不是构造函数
Loaded += async (_, _) =>
{
await LoadDataAsync();
};
}
private async Task LoadDataAsync()
{
var data = await _dataService.GetDataAsync();
// 更新 UI
}
}
```
### 3. 正确处理资源释放
```csharp
public class MyWidget : Border, IDisposable
{
private readonly Timer _timer;
private bool _disposed;
public MyWidget()
{
_timer = new Timer(OnTimerTick, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
// 在卸载时释放资源
Unloaded += (_, _) => Dispose();
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_timer?.Dispose();
}
}
```
### 4. 避免循环依赖
```csharp
// ❌ 避免:服务之间相互依赖
public class ServiceA
{
public ServiceA(ServiceB b) { } // 循环依赖风险
}
public class ServiceB
{
public ServiceB(ServiceA a) { }
}
// ✅ 好的做法:使用接口解耦
public class ServiceA
{
public ServiceA(IServiceB b) { }
}
```
---
## 🐛 常见问题
### 问题 1Initialize 中访问 UI 报错
**现象:** `InvalidOperationException` 或空引用
**原因:** Initialize 在 UI 就绪前调用
**解决:** 延迟到组件创建后再访问 UI
### 问题 2服务注册顺序问题
**现象:** 依赖注入找不到服务
**原因:** 服务注册顺序不正确
**解决:** 先注册服务,再注册依赖这些服务的组件
### 问题 3插件加载慢
**现象:** 宿主启动变慢
**原因:** Initialize 中执行耗时操作
**解决:** 将耗时操作移到后台线程或延迟执行
---
## 📚 参考资源
- [PluginBase 源码](../../LanMountainDesktop.PluginSdk/PluginBase.cs)
- [IPlugin 接口](../../LanMountainDesktop.PluginSdk/IPlugin.cs)
- [02-桌面组件系统](02-桌面组件系统.md)
- [03-设置系统集成](03-设置系统集成.md)
---
## 🎯 下一步
理解生命周期后,学习如何创建桌面组件:
👉 **[02-桌面组件系统](02-桌面组件系统.md)** - 创建可视化组件
---
*最后更新2026年4月*

View File

@@ -0,0 +1,393 @@
# 02-桌面组件系统
桌面组件Desktop Component是阑山桌面插件的核心功能。本文详细讲解组件系统的工作原理和开发方法。
---
## 🎯 什么是桌面组件
桌面组件是显示在阑山桌面上的可视化元素,用户可以自由:
- 添加/删除组件
- 拖动调整位置
- 调整大小
- 配置属性
**常见组件示例:**
- 时钟组件 - 显示当前时间
- 天气组件 - 显示天气信息
- 日历组件 - 显示日期和日程
- 系统监控 - 显示 CPU/内存使用率
---
## 📐 网格系统
阑山桌面使用网格系统管理组件布局。
### 网格概念
```
┌─────────────────────────────────────────┐
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ 2x2│ │ 2x2│ │ 2x2│ │ 2x2│ │
│ │ 格 │ │ 格 │ │ 格 │ │ 格 │ │
│ └────┘ └────┘ └────┘ └────┘ │
│ ┌────┐ ┌────────┐ ┌────┐ │
│ │ 2x2│ │ 4x2 │ │ 2x2│ │
│ │ 格 │ │ 格 │ │ 格 │ │
│ └────┘ └────────┘ └────┘ │
│ ┌────────┐ ┌────────┐ │
│ │ 4x3 │ │ 4x3 │ │
│ │ 格 │ │ 格 │ │
│ └────────┘ └────────┘ │
└─────────────────────────────────────────┘
每格大小:约 60-80 像素(根据 DPI 自动调整)
```
### 组件尺寸
组件尺寸以**格数**为单位:
| 属性 | 说明 | 示例 |
|-----|------|------|
| `MinWidthCells` | 最小宽度(格数) | 4 = 4格宽 |
| `MinHeightCells` | 最小高度(格数) | 3 = 3格高 |
**常见尺寸参考:**
| 组件类型 | 推荐尺寸 | 实际像素(约) |
|---------|---------|--------------|
| 小图标 | 2x2 | 120x120 |
| 天气卡片 | 4x3 | 240x180 |
| 时钟 | 4x4 | 240x240 |
| 日历 | 6x4 | 360x240 |
| 宽面板 | 8x3 | 480x180 |
---
## 🏗️ 创建组件
### 步骤 1创建组件类
组件是继承自 Avalonia 控件的类:
```csharp
using Avalonia.Controls;
using LanMountainDesktop.PluginSdk;
namespace MyPlugin;
public class WeatherWidget : Border // 继承自 Border 或其他控件
{
public WeatherWidget(PluginDesktopComponentContext context)
{
// 组件初始化
InitializeComponent(context);
}
private void InitializeComponent(PluginDesktopComponentContext context)
{
// 设置背景
Background = new SolidColorBrush(Colors.Transparent);
// 设置圆角(使用宿主主题)
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
// 创建内容
var textBlock = new TextBlock
{
Text = "天气组件",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
Child = textBlock;
}
}
```
### 步骤 2注册组件
`Plugin.Initialize` 中注册:
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddPluginDesktopComponent<WeatherWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "MyPlugin.Weather", // 唯一标识
DisplayName = "天气", // 显示名称
IconKey = "Weather", // 图标Fluent 图标名)
Category = "工具", // 分类
MinWidthCells = 4, // 最小宽度(格)
MinHeightCells = 3, // 最小高度(格)
CornerRadiusPreset = PluginCornerRadiusPreset.Component // 圆角预设
});
}
```
### PluginDesktopComponentOptions 详解
| 属性 | 必需 | 说明 | 示例 |
|-----|------|------|------|
| `ComponentId` | ✅ | 唯一标识符 | `"MyPlugin.Weather"` |
| `DisplayName` | ✅ | 显示名称 | `"天气"` |
| `IconKey` | ✅ | 图标键名 | `"Weather"``"Clock"` |
| `Category` | ✅ | 分类 | `"工具"``"信息"` |
| `MinWidthCells` | ✅ | 最小宽度(格) | `4` |
| `MinHeightCells` | ✅ | 最小高度(格) | `3` |
| `CornerRadiusPreset` | ❌ | 圆角预设 | `PluginCornerRadiusPreset.Component` |
| `ResizeMode` | ❌ | 调整大小模式 | `PluginDesktopComponentResizeMode.Free` |
### 常用 Fluent 图标
| 图标键名 | 用途 |
|---------|------|
| `Weather` | 天气相关 |
| `Clock` | 时钟、时间 |
| `Calendar` | 日历、日期 |
| `Settings` | 设置 |
| `Home` | 主页 |
| `Search` | 搜索 |
| `Star` | 收藏 |
| `Heart` | 喜欢 |
| `Info` | 信息 |
| `Warning` | 警告 |
完整图标列表:[Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons)
---
## 🎨 组件外观
### 圆角设置
插件必须使用宿主提供的圆角系统,以保持视觉一致性:
```csharp
public WeatherWidget(PluginDesktopComponentContext context)
{
// 获取组件标准圆角
var cornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
CornerRadius = cornerRadius;
}
```
**可用的圆角预设:**
| 预设 | 用途 |
|-----|------|
| `Micro` | 微小元素 |
| `Xs` | 小元素 |
| `Sm` | 小卡片 |
| `Md` | 普通按钮/卡片 |
| `Lg` | 大面板 |
| `Xl` | 强调容器 |
| `Component` | **桌面组件标准** |
| `Default` | 自适应 |
### 背景与透明
```csharp
// 透明背景(推荐,让宿主壁纸透出)
Background = new SolidColorBrush(Colors.Transparent);
// 毛玻璃效果
Background = new SolidColorBrush(Color.Parse("#40FFFFFF"));
// 纯色背景
Background = new SolidColorBrush(Color.Parse("#FF2D2D2D"));
```
### 响应主题变化
```csharp
public WeatherWidget(PluginDesktopComponentContext context)
{
// 订阅主题变化
context.Appearance.AppearanceChanged += (_, _) =>
{
UpdateAppearance();
};
UpdateAppearance();
}
private void UpdateAppearance()
{
// 根据当前主题更新颜色
var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
Foreground = new SolidColorBrush(isDark ? Colors.White : Colors.Black);
}
```
---
## 📏 尺寸与布局
### 获取实际尺寸
```csharp
public WeatherWidget(PluginDesktopComponentContext context)
{
// 订阅尺寸变化
SizeChanged += OnSizeChanged;
}
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{
// 获取当前实际尺寸(像素)
var width = Bounds.Width;
var height = Bounds.Height;
// 根据尺寸调整内容
if (width < 200)
{
// 小尺寸模式
ShowCompactView();
}
else
{
// 完整模式
ShowFullView();
}
}
```
### 自适应布局
```csharp
private void UpdateLayout()
{
var width = Bounds.Width;
var height = Bounds.Height;
// 根据宽高比调整布局
if (width > height * 2)
{
// 宽屏模式 - 水平排列
_layout.Orientation = Orientation.Horizontal;
}
else
{
// 正常模式 - 垂直排列
_layout.Orientation = Orientation.Vertical;
}
}
```
---
## 🔄 组件生命周期事件
```csharp
public WeatherWidget(PluginDesktopComponentContext context)
{
// 组件加载完成(此时已添加到视觉树)
Loaded += OnLoaded;
// 组件卸载(用户删除或关闭宿主)
Unloaded += OnUnloaded;
// 尺寸变化
SizeChanged += OnSizeChanged;
}
private async void OnLoaded(object? sender, RoutedEventArgs e)
{
// 加载数据
await LoadDataAsync();
// 启动定时器
_timer = new Timer(OnTimerTick, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
// 清理资源
_timer?.Dispose();
_httpClient?.Dispose();
}
```
---
## 💾 组件设置持久化
组件可以保存自己的设置:
```csharp
public class WeatherWidget : Border
{
private readonly IComponentSettingsAccessor _settings;
public WeatherWidget(PluginDesktopComponentContext context)
{
// 获取设置访问器
_settings = context.Settings;
// 读取设置
var city = _settings.GetValue<string>("city", defaultValue: "北京");
var autoRefresh = _settings.GetValue<bool>("auto_refresh", defaultValue: true);
// 保存设置
_settings.SetValue("city", "上海");
}
}
```
---
## 🐛 常见问题
### 问题 1组件不显示在库中
**排查:**
1. 确认已调用 `AddPluginDesktopComponent`
2. 检查 `ComponentId` 是否唯一
3. 确认组件类是 `public`
### 问题 2组件显示异常
**排查:**
1. 检查构造函数参数是否正确(需要 `PluginDesktopComponentContext`
2. 确认没有抛出未处理异常
3. 查看日志文件
### 问题 3圆角不生效
**原因:** 插件无法访问宿主 XAML 资源
**解决:** 使用代码设置圆角(见上文)
### 问题 4尺寸不正确
**排查:**
1. 检查 `MinWidthCells``MinHeightCells` 设置
2. 确认内容没有强制尺寸
---
## 📚 参考资源
- [PluginDesktopComponentOptions 源码](../../LanMountainDesktop.PluginSdk/PluginDesktopComponentOptions.cs)
- [04-外观与主题系统](04-外观与主题系统.md)
- [01-开发天气组件](../04-实战案例/01-开发天气组件.md)
---
## 🎯 下一步
学习如何添加设置页面:
👉 **[03-设置系统集成](03-设置系统集成.md)** - 让用户配置你的组件
---
*最后更新2026年4月*

View File

@@ -0,0 +1,353 @@
# 03-设置系统集成
设置系统允许插件在阑山桌面的设置窗口中添加自己的配置页面,让用户可以自定义插件行为。
---
## 🎯 设置系统概述
阑山桌面提供两种设置页面模式:
| 模式 | 适用场景 | 复杂度 | 灵活性 |
|-----|---------|--------|--------|
| **声明式设置** | 简单的键值配置 | 低 | 中 |
| **自定义设置页** | 复杂交互、自定义控件 | 中 | 高 |
---
## 📝 声明式设置
通过链式 API 声明配置项,宿主自动生成设置页面。
### 基本用法
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddPluginSettingsSection(
sectionId: "myplugin-settings", // 设置节唯一标识
displayName: "我的插件设置", // 显示名称
configure: section => // 配置设置项
{
section
.AddToggle("enabled", "启用插件", defaultValue: true)
.AddText("api_key", "API密钥", defaultValue: "")
.AddNumber("interval", "刷新间隔(秒)", defaultValue: 60, minimum: 10, maximum: 3600)
.AddSelect("theme", "主题", new[]
{
new SettingsOptionChoice("light", "浅色"),
new SettingsOptionChoice("dark", "深色"),
new SettingsOptionChoice("auto", "跟随系统")
}, defaultValue: "auto");
},
iconKey: "Settings"); // 图标
}
```
### 支持的设置类型
| 方法 | 类型 | 用途 | 示例 |
|-----|------|------|------|
| `AddToggle` | 布尔 | 开关选项 | 启用/禁用 |
| `AddText` | 字符串 | 文本输入 | API密钥、用户名 |
| `AddNumber` | 数值 | 数字输入 | 刷新间隔、数量 |
| `AddSelect` | 枚举 | 下拉选择 | 主题、语言 |
| `AddPath` | 路径 | 文件/文件夹选择 | 保存路径 |
| `AddList` | 列表 | 字符串列表 | 服务器地址列表 |
### 各类型详解
#### Toggle开关
```csharp
.AddToggle(
key: "auto_update", // 设置键
displayName: "自动更新", // 显示名称
defaultValue: true, // 默认值
description: "启动时检查更新" // 可选描述
)
```
#### Text文本
```csharp
.AddText(
key: "api_key",
displayName: "API密钥",
defaultValue: "",
placeholder: "请输入API密钥", // 占位符
isPassword: true // 密码输入(掩码显示)
)
```
#### Number数值
```csharp
.AddNumber(
key: "refresh_interval",
displayName: "刷新间隔",
defaultValue: 60,
minimum: 10, // 最小值
maximum: 3600, // 最大值
increment: 10 // 步进值
)
```
#### Select选择
```csharp
.AddSelect(
key: "display_mode",
displayName: "显示模式",
choices: new[]
{
new SettingsOptionChoice("compact", "紧凑"),
new SettingsOptionChoice("normal", "标准"),
new SettingsOptionChoice("detailed", "详细")
},
defaultValue: "normal"
)
```
#### Path路径
```csharp
.AddPath(
key: "save_location",
displayName: "保存位置",
defaultValue: "",
pathType: SettingsPathType.Folder, // Folder 或 File
dialogTitle: "选择保存文件夹"
)
```
---
## 🎨 自定义设置页
当声明式设置无法满足需求时,可以创建自定义设置页面。
### 步骤 1创建设置页类
```csharp
using LanMountainDesktop.PluginSdk;
using FluentAvalonia.UI.Controls;
namespace MyPlugin;
public class MySettingsPage : SettingsPageBase
{
private readonly IPluginSettingsService _settingsService;
public MySettingsPage(IPluginSettingsService settingsService)
{
_settingsService = settingsService;
InitializeComponent();
}
private void InitializeComponent()
{
// 创建页面内容
var panel = new StackPanel { Spacing = 16 };
// 添加自定义控件
var expander = new SettingsExpander
{
Header = "高级设置",
Description = "配置插件的高级选项"
};
var toggle = new ToggleSwitch
{
Content = "启用实验性功能"
};
expander.Items.Add(toggle);
panel.Children.Add(expander);
// 添加颜色选择器示例
var colorPicker = new ColorPicker
{
Header = "主题颜色"
};
panel.Children.Add(colorPicker);
Content = panel;
}
}
```
### 步骤 2注册自定义设置页
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddPluginSettingsSection<MySettingsPage>(
sectionId: "myplugin-advanced",
displayName: "高级设置",
iconKey: "Settings");
}
```
### 混合模式
可以同时使用声明式设置和自定义视图:
```csharp
services.AddPluginSettingsSection(
sectionId: "myplugin-settings",
displayName: "插件设置",
configure: section => section
.SetCustomView<MyCustomSettingsPage>() // 设置自定义视图
.AddToggle("enabled", "启用") // 同时声明设置项
.AddText("api_key", "API密钥"),
iconKey: "Settings");
```
---
## 💾 读取和保存设置
### 在服务中读取设置
```csharp
public class WeatherService
{
private readonly IPluginSettingsService _settings;
public WeatherService(IPluginSettingsService settings)
{
_settings = settings;
// 读取设置
var apiKey = _settings.GetValue<string>("api_key", "");
var autoRefresh = _settings.GetValue<bool>("auto_update", true);
var interval = _settings.GetValue<int>("refresh_interval", 60);
// 监听设置变化
_settings.SettingsChanged += (sender, e) =>
{
if (e.Key == "refresh_interval")
{
UpdateTimerInterval(e.NewValue);
}
};
}
}
```
### 在组件中读取设置
```csharp
public class WeatherWidget : Border
{
public WeatherWidget(PluginDesktopComponentContext context)
{
// 通过 context 获取设置
var settings = context.Settings;
var city = settings.GetValue<string>("city", "北京");
var unit = settings.GetValue<string>("temperature_unit", "celsius");
// 监听设置变化
context.Settings.SettingsChanged += (_, e) =>
{
if (e.Key == "city")
{
RefreshWeather(e.NewValue);
}
};
}
}
```
### 保存设置
```csharp
// 在设置页面中保存
private void SaveButton_Click(object? sender, RoutedEventArgs e)
{
_settingsService.SetValue("api_key", ApiKeyTextBox.Text);
_settingsService.SetValue("auto_update", AutoUpdateToggle.IsChecked ?? false);
// 设置会自动持久化,无需手动保存文件
}
```
---
## 🔔 设置变更通知
### 订阅变更事件
```csharp
public class MyService
{
public MyService(IPluginSettingsService settings)
{
settings.SettingsChanged += OnSettingsChanged;
}
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
{
Console.WriteLine($"设置变更: {e.Key}");
Console.WriteLine($"旧值: {e.OldValue}");
Console.WriteLine($"新值: {e.NewValue}");
// 根据变更的键执行相应操作
switch (e.Key)
{
case "refresh_interval":
UpdateRefreshTimer((int)e.NewValue);
break;
case "theme":
ApplyTheme((string)e.NewValue);
break;
}
}
}
```
---
## 🐛 常见问题
### 问题 1设置不保存
**排查:**
1. 确认 `sectionId` 唯一且合法
2. 检查设置键名是否正确
3. 查看日志是否有权限错误
### 问题 2设置页面不显示
**排查:**
1. 确认已调用 `AddPluginSettingsSection`
2. 检查 `sectionId` 是否唯一
3. 确认设置页类是 `public`
### 问题 3设置变更通知不触发
**原因:** 可能订阅的是不同实例
**解决:** 确保使用注入的 `IPluginSettingsService`
---
## 📚 参考资源
- [IPluginSettingsService 源码](../../LanMountainDesktop.PluginSdk/IPluginSettingsService.cs)
- [SettingsPageBase 源码](../../LanMountainDesktop.PluginSdk/SettingsPageBase.cs)
- [04-开发设置页面](../04-实战案例/04-开发设置页面.md)
---
## 🎯 下一步
学习外观系统:
👉 **[04-外观与主题系统](04-外观与主题系统.md)** - 适配宿主主题
---
*最后更新2026年4月*

View File

@@ -0,0 +1,308 @@
# 04-外观与主题系统
阑山桌面支持暗色/浅色主题切换,插件需要适配宿主的视觉风格,保持界面一致性。
---
## 🎨 主题系统概述
阑山桌面使用 Avalonia UI 的主题系统,支持:
- **浅色主题** - 明亮背景,深色文字
- **深色主题** - 深色背景,浅色文字
- **跟随系统** - 自动匹配 Windows/macOS 主题
---
## 🌗 检测当前主题
### 在组件中检测
```csharp
using Avalonia;
using Avalonia.Styling;
public class MyWidget : Border
{
public MyWidget(PluginDesktopComponentContext context)
{
// 检测当前主题
var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
// 根据主题设置颜色
UpdateTheme(isDark);
// 监听主题变化
if (Application.Current != null)
{
Application.Current.ActualThemeVariantChanged += (_, _) =>
{
var newIsDark = Application.Current.ActualThemeVariant == ThemeVariant.Dark;
UpdateTheme(newIsDark);
};
}
}
private void UpdateTheme(bool isDark)
{
Background = new SolidColorBrush(
isDark ? Color.Parse("#FF1E1E1E") : Color.Parse("#FFFFFFFF"));
Foreground = new SolidColorBrush(
isDark ? Colors.White : Colors.Black);
}
}
```
---
## 📐 圆角系统
插件必须使用宿主提供的圆角系统,确保与内置组件视觉一致。
### 为什么插件不能使用 XAML 资源
插件运行在独立的 `AssemblyLoadContext` 中,无法直接访问宿主的资源字典。因此 `{DynamicResource DesignCornerRadiusComponent}` 在插件 XAML 中无效。
### 使用代码设置圆角
```csharp
public class MyWidget : Border
{
public MyWidget(PluginDesktopComponentContext context)
{
// 方法 1使用预设推荐
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
// 方法 2带最小/最大值限制
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component,
minimum: new CornerRadius(8),
maximum: new CornerRadius(24));
// 方法 3自定义基础值应用全局缩放
CornerRadius = context.Appearance.ResolveScaledCornerRadius(
baseRadius: 16,
minimum: 8,
maximum: 32);
}
}
```
### 圆角预设
| 预设 | 默认值 | 用途 |
|-----|-------|------|
| `Micro` | 6px | 微小元素 |
| `Xs` | 12px | 小元素、图标容器 |
| `Sm` | 14px | 小卡片 |
| `Md` | 20px | 普通按钮/卡片 |
| `Lg` | 28px | 大面板 |
| `Xl` | 32px | 强调容器 |
| `Island` | 36px | 大型容器 |
| `Component` | 18px | **桌面组件标准** |
| `Default` | 自适应 | 根据尺寸自动计算 |
### 内部元素圆角
组件内部的卡片、按钮应使用更小的圆角:
```csharp
// 组件根容器 - 使用 Component 预设
CornerRadius = context.ResolveCornerRadius(PluginCornerRadiusPreset.Component);
// 内部卡片 - 使用 Md 预设
var innerCard = new Border
{
CornerRadius = context.ResolveCornerRadius(PluginCornerRadiusPreset.Md),
Background = new SolidColorBrush(Colors.LightGray)
};
// 按钮 - 使用 Sm 预设
var button = new Button
{
CornerRadius = context.ResolveCornerRadius(PluginCornerRadiusPreset.Sm)
};
```
---
## 🎨 颜色系统
### 推荐的颜色策略
```csharp
// 透明背景(推荐)- 让宿主壁纸透出
Background = new SolidColorBrush(Colors.Transparent);
// 毛玻璃效果
Background = new SolidColorBrush(Color.Parse(isDark ? "#40FFFFFF" : "#40000000"));
// 卡片背景
Background = new SolidColorBrush(Color.Parse(isDark ? "#FF2D2D2D" : "#FFFFFFFF"));
// 强调色(使用系统强调色)
var accentColor = Color.Parse("#FF0078D4"); // 阑山桌面主色调
```
### 文字颜色
```csharp
// 主要文字
Foreground = new SolidColorBrush(isDark ? Colors.White : Colors.Black);
// 次要文字
Foreground = new SolidColorBrush(isDark ? Color.Parse("#FFCCCCCC") : Color.Parse("#FF666666"));
// 禁用文字
Foreground = new SolidColorBrush(isDark ? Color.Parse("#FF666666") : Color.Parse("#FF999999"));
```
---
## 🔄 响应外观变化
### 订阅外观变化事件
```csharp
public class MyWidget : Border
{
public MyWidget(PluginDesktopComponentContext context)
{
// 订阅外观变化
context.Appearance.AppearanceChanged += (_, _) =>
{
UpdateAppearance();
};
// 初始化
UpdateAppearance();
}
private void UpdateAppearance()
{
// 重新应用圆角(用户可能调整了全局圆角设置)
var context = ...; // 获取 context
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
// 更新主题颜色
var isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
UpdateThemeColors(isDark);
}
}
```
---
## 🧩 使用 FluentAvalonia 控件
推荐使用 FluentAvalonia 控件库,它们自动适配主题:
```xml
<Window xmlns:ui="using:FluentAvalonia.UI.Controls">
<ui:SettingsExpander Header="设置项">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource Glyph="&#xE713;" />
</ui:SettingsExpander.IconSource>
</ui:SettingsExpander>
</Window>
```
### 常用 FluentAvalonia 控件
| 控件 | 用途 |
|-----|------|
| `SettingsExpander` | 设置项展开器 |
| `SettingsCard` | 设置卡片 |
| `ColorPicker` | 颜色选择器 |
| `NumberBox` | 数字输入框 |
| `ToggleSwitch` | 开关 |
---
## 💡 最佳实践
### 1. 始终使用透明背景
```csharp
// ✅ 好的做法
Background = new SolidColorBrush(Colors.Transparent);
// ❌ 避免硬编码背景色
Background = new SolidColorBrush(Colors.White);
```
### 2. 组件根容器必须使用 Component 圆角
```csharp
// ✅ 正确
CornerRadius = context.ResolveCornerRadius(PluginCornerRadiusPreset.Component);
// ❌ 错误 - 硬编码
CornerRadius = new CornerRadius(18);
```
### 3. 响应主题变化
```csharp
// ✅ 订阅变化事件
Application.Current.ActualThemeVariantChanged += OnThemeChanged;
// ❌ 只在构造函数中设置一次
```
### 4. 使用语义化颜色
```csharp
// ✅ 根据用途选择颜色
var primaryText = isDark ? Colors.White : Colors.Black;
var secondaryText = isDark ? Color.Parse("#FFCCCCCC") : Color.Parse("#FF666666");
// ❌ 避免随意使用颜色
var textColor = Color.Parse("#FF123456");
```
---
## 🐛 常见问题
### 问题 1圆角不生效
**原因:** 在 XAML 中使用 `{DynamicResource}`
**解决:** 在代码中设置圆角(见上文)
### 问题 2主题切换后颜色不对
**原因:** 没有订阅主题变化事件
**解决:** 添加 `ActualThemeVariantChanged` 事件处理
### 问题 3组件与内置组件风格不一致
**排查:**
1. 检查圆角是否使用 `PluginCornerRadiusPreset.Component`
2. 检查背景是否透明
3. 检查是否使用了 FluentAvalonia 控件
---
## 📚 参考资源
- [CORNER_RADIUS_SPEC.md](../../docs/CORNER_RADIUS_SPEC.md)
- [VISUAL_SPEC.md](../../docs/VISUAL_SPEC.md)
- [FluentAvalonia 文档](https://github.com/amwx/FluentAvalonia)
---
## 🎯 下一步
学习插件间通信:
👉 **[05-插件间通信](05-插件间通信.md)** - 与其他插件协作
---
*最后更新2026年4月*

View File

@@ -0,0 +1,375 @@
# 05-插件间通信
插件之间可以通过消息总线和服务导出进行通信,实现功能协作和数据共享。
---
## 🎯 通信方式概述
| 方式 | 适用场景 | 方向 |
|-----|---------|------|
| **消息总线** | 事件通知、广播 | 多对多 |
| **服务导出** | 功能共享、API 暴露 | 一对多 |
| **共享契约** | 数据交换 | 双向 |
---
## 📢 消息总线
使用 `IPluginMessageBus` 发布和订阅消息。
### 发布消息
```csharp
public class WeatherService
{
private readonly IPluginMessageBus _messageBus;
public WeatherService(IPluginMessageBus messageBus)
{
_messageBus = messageBus;
}
public async Task UpdateWeatherAsync()
{
var weather = await FetchWeatherAsync();
// 发布天气更新消息
_messageBus.Publish(new WeatherUpdatedMessage
{
City = weather.City,
Temperature = weather.Temperature,
Condition = weather.Condition
});
}
}
// 定义消息
public class WeatherUpdatedMessage
{
public string City { get; set; } = "";
public double Temperature { get; set; }
public string Condition { get; set; } = "";
}
```
### 订阅消息
```csharp
public class AnotherPluginService
{
public AnotherPluginService(IPluginMessageBus messageBus)
{
// 订阅天气更新消息
messageBus.Subscribe<WeatherUpdatedMessage>(OnWeatherUpdated);
}
private void OnWeatherUpdated(WeatherUpdatedMessage message)
{
Console.WriteLine($"收到天气更新: {message.City} {message.Temperature}°C");
// 根据天气更新自己的状态
UpdateDisplay(message);
}
}
```
### 取消订阅
```csharp
public class MyService : IDisposable
{
private readonly IPluginMessageBus _messageBus;
private readonly Guid _subscriptionId;
public MyService(IPluginMessageBus messageBus)
{
_messageBus = messageBus;
_subscriptionId = messageBus.Subscribe<WeatherUpdatedMessage>(OnWeatherUpdated);
}
public void Dispose()
{
// 取消订阅,避免内存泄漏
_messageBus.Unsubscribe<WeatherUpdatedMessage>(_subscriptionId);
}
}
```
---
## 🔌 服务导出
插件可以将服务导出,供其他插件使用。
### 导出服务
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 注册服务
services.AddSingleton<IWeatherService, WeatherService>();
// 导出服务供其他插件使用
services.AddPluginServiceExport<IWeatherService>(
serviceKey: "MyPlugin.WeatherService",
description: "提供天气查询服务");
}
// 定义服务接口
public interface IWeatherService
{
Task<WeatherInfo> GetCurrentWeatherAsync(string city);
Task<List<WeatherForecast>> GetForecastAsync(string city, int days);
}
// 实现服务
public class WeatherService : IWeatherService
{
public async Task<WeatherInfo> GetCurrentWeatherAsync(string city)
{
// 实现天气查询
}
public async Task<List<WeatherForecast>> GetForecastAsync(string city, int days)
{
// 实现天气预报
}
}
```
### 使用其他插件的服务
```csharp
public class MyWidget : Border
{
public MyWidget(PluginDesktopComponentContext context)
{
// 获取其他插件导出的服务
var weatherService = context.ServiceProvider
.GetExportedService<IWeatherService>("MyPlugin.WeatherService");
if (weatherService != null)
{
// 使用服务
LoadWeatherAsync(weatherService);
}
}
private async void LoadWeatherAsync(IWeatherService weatherService)
{
var weather = await weatherService.GetCurrentWeatherAsync("北京");
UpdateUI(weather);
}
}
```
### 服务导出选项
```csharp
services.AddPluginServiceExport<IWeatherService>(
serviceKey: "MyPlugin.WeatherService",
description: "提供天气查询服务",
version: "1.0.0",
isPublic: true); // 是否公开给其他插件
```
---
## 📦 共享契约
通过 `sharedContracts` 在插件间共享类型定义。
### 定义共享契约
```csharp
// 在共享类库项目中定义
namespace MyPlugin.Shared;
public interface IWeatherData
{
string City { get; }
double Temperature { get; }
string Condition { get; }
}
public class WeatherData : IWeatherData
{
public string City { get; set; } = "";
public double Temperature { get; set; }
public string Condition { get; set; } = "";
}
```
### 在 plugin.json 中声明
```json
{
"id": "com.example.weather",
"name": "天气插件",
"sharedContracts": [
"MyPlugin.Shared.IWeatherData",
"MyPlugin.Shared.WeatherData"
]
}
```
### 使用共享类型
```csharp
// 插件 A 发布数据
public class WeatherService
{
public WeatherData GetWeather()
{
return new WeatherData
{
City = "北京",
Temperature = 25.5,
Condition = "晴"
};
}
}
// 插件 B 接收数据
public class ConsumerService
{
public void ProcessWeather(IWeatherData weather)
{
Console.WriteLine($"{weather.City}: {weather.Temperature}°C");
}
}
```
---
## 🔒 安全考虑
### 服务导出安全
```csharp
// 只导出必要的接口,不暴露实现细节
public interface IPublicApi
{
Task<Data> GetDataAsync();
}
internal class InternalService : IPublicApi
{
// 内部实现细节不暴露
private readonly SecretKey _key;
public async Task<Data> GetDataAsync()
{
// 实现
}
}
```
### 消息验证
```csharp
private void OnMessageReceived(MyMessage message)
{
// 验证消息来源
if (message.SenderId != "TrustedPlugin")
{
return; // 忽略不信任来源的消息
}
// 处理消息
}
```
---
## 💡 最佳实践
### 1. 使用接口定义服务契约
```csharp
// ✅ 好的做法 - 定义接口
public interface IWeatherService { }
// ❌ 避免 - 直接导出实现类
services.AddPluginServiceExport<WeatherService>(...);
```
### 2. 处理服务不可用情况
```csharp
// ✅ 优雅处理服务缺失
var service = context.ServiceProvider
.GetExportedService<IWeatherService>("key");
if (service == null)
{
// 显示提示或降级处理
ShowServiceUnavailableMessage();
return;
}
```
### 3. 及时取消消息订阅
```csharp
// ✅ 在 Dispose 中取消订阅
public void Dispose()
{
_messageBus.Unsubscribe<MyMessage>(_subscriptionId);
}
```
### 4. 版本兼容性
```csharp
// 在服务导出中包含版本信息
services.AddPluginServiceExport<IWeatherService>(
serviceKey: "MyPlugin.WeatherService",
version: "2.0.0", // 语义化版本
description: "天气服务 v2");
```
---
## 🐛 常见问题
### 问题 1消息收不到
**排查:**
1. 确认消息类型完全一致(包括命名空间)
2. 检查订阅是否在消息发布之前
3. 确认没有取消订阅
### 问题 2服务找不到
**排查:**
1. 确认服务已导出(`AddPluginServiceExport`
2. 检查 `serviceKey` 是否正确
3. 确认依赖的插件已安装并启用
### 问题 3类型转换错误
**原因:** 共享契约类型不匹配
**解决:** 确保所有插件使用相同版本的共享契约程序集
---
## 📚 参考资源
- [IPluginMessageBus 源码](../../LanMountainDesktop.PluginSdk/IPluginMessageBus.cs)
- [IPluginExportRegistry 源码](../../LanMountainDesktop.PluginSdk/IPluginExportRegistry.cs)
- [Shared.Contracts](../../LanMountainDesktop.Shared.Contracts/)
---
## 🎯 下一步
查看实战案例:
👉 **[01-开发天气组件](../04-实战案例/01-开发天气组件.md)** - 完整插件开发流程
---
*最后更新2026年4月*

View File

@@ -0,0 +1,307 @@
# 01-PluginBase详解
`PluginBase` 是插件的基类,提供基础功能和生命周期管理。本文详细讲解其用法和扩展点。
---
## 🎯 PluginBase 概述
```csharp
public abstract class PluginBase : IPlugin
{
// 日志记录器
protected ILogger? Logger { get; }
// 初始化方法(必须实现)
public abstract void Initialize(HostBuilderContext context, IServiceCollection services);
}
```
---
## 📝 基本用法
### 最小实现
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace MyPlugin;
[PluginEntrance]
public sealed class Plugin : PluginBase
{
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 插件初始化逻辑
}
}
```
---
## 🔧 Initialize 方法详解
### 方法签名
```csharp
public abstract void Initialize(
HostBuilderContext context, // 宿主构建上下文
IServiceCollection services // 服务注册集合
);
```
### 参数说明
| 参数 | 类型 | 用途 |
|-----|------|------|
| `context` | `HostBuilderContext` | 访问宿主配置、环境信息 |
| `services` | `IServiceCollection` | 注册服务、组件、设置页面 |
### context 使用示例
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 访问配置
var configValue = context.Configuration["MySetting"];
// 判断运行环境
var isDevelopment = context.HostingEnvironment.IsDevelopment();
// 获取应用名称
var appName = context.HostingEnvironment.ApplicationName;
}
```
---
## 📝 日志记录
### 使用 Logger 属性
```csharp
[PluginEntrance]
public sealed class Plugin : PluginBase
{
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 记录日志
Logger?.LogInformation("插件初始化开始");
try
{
// 初始化逻辑
services.AddSingleton<IMyService, MyService>();
Logger?.LogInformation("插件初始化完成");
}
catch (Exception ex)
{
Logger?.LogError(ex, "插件初始化失败");
throw;
}
}
}
```
### 日志级别
```csharp
Logger?.LogTrace("详细跟踪信息");
Logger?.LogDebug("调试信息");
Logger?.LogInformation("一般信息");
Logger?.LogWarning("警告信息");
Logger?.LogError("错误信息");
Logger?.LogCritical("严重错误");
```
---
## 🔌 服务注册
### 注册单例服务
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 单例 - 整个应用生命周期只有一个实例
services.AddSingleton<IWeatherService, WeatherService>();
}
```
### 注册作用域服务
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 作用域 - 每个作用域一个实例
services.AddScoped<IDataContext, DataContext>();
}
```
### 注册瞬态服务
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 瞬态 - 每次请求都创建新实例
services.AddTransient<IValidator, Validator>();
}
```
### 带配置的服务注册
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddSingleton<IWeatherService>(provider =>
{
var httpClient = provider.GetRequiredService<HttpClient>();
var logger = provider.GetRequiredService<ILogger<WeatherService>>();
var apiKey = context.Configuration["WeatherApiKey"];
return new WeatherService(httpClient, logger, apiKey);
});
}
```
---
## 🧩 完整示例
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WeatherPlugin;
[PluginEntrance]
public sealed class Plugin : PluginBase
{
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
Logger?.LogInformation("天气插件初始化开始");
try
{
// 1. 注册 HTTP 客户端
services.AddHttpClient("weather", client =>
{
client.BaseAddress = new Uri("https://api.weather.com/");
client.Timeout = TimeSpan.FromSeconds(30);
});
// 2. 注册服务
services.AddSingleton<IWeatherService, WeatherService>();
services.AddSingleton<ILocationService, LocationService>();
// 3. 注册桌面组件
services.AddPluginDesktopComponent<WeatherWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "WeatherPlugin.Widget",
DisplayName = "天气",
IconKey = "Weather",
Category = "信息",
MinWidthCells = 4,
MinHeightCells = 3
});
// 4. 注册设置页面
services.AddPluginSettingsSection(
"weather-settings",
"天气设置",
section => section
.AddText("api_key", "API密钥", isPassword: true)
.AddText("default_city", "默认城市", defaultValue: "北京")
.AddToggle("auto_refresh", "自动刷新", defaultValue: true)
.AddNumber("refresh_interval", "刷新间隔(分钟)",
defaultValue: 30, minimum: 5, maximum: 120),
iconKey: "Settings");
Logger?.LogInformation("天气插件初始化完成");
}
catch (Exception ex)
{
Logger?.LogError(ex, "天气插件初始化失败");
throw;
}
}
}
```
---
## 💡 最佳实践
### 1. 使用 try-catch 包装初始化逻辑
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
try
{
// 初始化逻辑
}
catch (Exception ex)
{
Logger?.LogError(ex, "初始化失败");
throw; // 重新抛出,让宿主知道初始化失败
}
}
```
### 2. 按依赖顺序注册服务
```csharp
// ✅ 先注册被依赖的服务
services.AddSingleton<IDataService, DataService>();
// 再注册依赖它们的服务
services.AddSingleton<IWeatherService, WeatherService>(); // 依赖 IDataService
// 最后注册组件
services.AddPluginDesktopComponent<WeatherWidget>(options);
```
### 3. 记录初始化过程
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
Logger?.LogInformation("开始初始化...");
Logger?.LogDebug("注册服务...");
services.AddSingleton<IMyService, MyService>();
Logger?.LogDebug("注册组件...");
services.AddPluginDesktopComponent<MyWidget>(options);
Logger?.LogInformation("初始化完成");
}
```
---
## 📚 参考资源
- [PluginBase 源码](../../LanMountainDesktop.PluginSdk/PluginBase.cs)
- [IPlugin 接口](../../LanMountainDesktop.PluginSdk/IPlugin.cs)
- [Microsoft.Extensions.DependencyInjection 文档](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection)
---
## 🎯 下一步
学习组件注册 API
👉 **[02-组件注册与配置](02-组件注册与配置.md)**
---
*最后更新2026年4月*

View File

@@ -0,0 +1,182 @@
# 02-组件注册与配置
`AddPluginDesktopComponent` 是注册桌面组件的核心 API。本文详细讲解其用法和配置选项。
---
## 🎯 API 概览
```csharp
public static IServiceCollection AddPluginDesktopComponent<TComponent>(
this IServiceCollection services,
PluginDesktopComponentOptions options)
where TComponent : class, IControl
```
---
## 📋 基本用法
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
services.AddPluginDesktopComponent<MyWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "MyPlugin.MyWidget",
DisplayName = "我的组件",
IconKey = "Home",
Category = "工具",
MinWidthCells = 4,
MinHeightCells = 3
});
}
```
---
## 🔧 PluginDesktopComponentOptions
### 完整属性列表
| 属性 | 类型 | 必需 | 说明 |
|-----|------|------|------|
| `ComponentId` | `string` | ✅ | 唯一标识符 |
| `DisplayName` | `string` | ✅ | 显示名称 |
| `IconKey` | `string` | ✅ | 图标键名 |
| `Category` | `string` | ✅ | 分类 |
| `MinWidthCells` | `int` | ✅ | 最小宽度(格) |
| `MinHeightCells` | `int` | ✅ | 最小高度(格) |
| `CornerRadiusPreset` | `PluginCornerRadiusPreset` | ❌ | 圆角预设 |
| `ResizeMode` | `PluginDesktopComponentResizeMode` | ❌ | 调整大小模式 |
### ComponentId
```csharp
ComponentId = "MyPlugin.WeatherWidget"
```
- 必须唯一
- 建议使用 `插件ID.组件名` 格式
- 一经发布不可更改
### DisplayName
```csharp
DisplayName = "天气"
```
- 显示在组件库中
- 支持本地化(通过资源文件)
### IconKey
```csharp
IconKey = "Weather"
```
使用 [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons) 的图标名。
### Category
```csharp
Category = "工具"
```
常用分类:
- `工具` - 实用工具
- `信息` - 信息展示
- `娱乐` - 娱乐相关
- `系统` - 系统监控
### MinWidthCells / MinHeightCells
```csharp
MinWidthCells = 4, // 4格宽约240像素
MinHeightCells = 3 // 3格高约180像素
```
---
## 🎨 圆角配置
```csharp
services.AddPluginDesktopComponent<MyWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "MyPlugin.Widget",
DisplayName = "我的组件",
IconKey = "Home",
Category = "工具",
MinWidthCells = 4,
MinHeightCells = 3,
CornerRadiusPreset = PluginCornerRadiusPreset.Component
});
```
---
## 📐 调整大小模式
```csharp
services.AddPluginDesktopComponent<MyWidget>(
new PluginDesktopComponentOptions
{
// ...
ResizeMode = PluginDesktopComponentResizeMode.Free
});
```
| 模式 | 说明 |
|-----|------|
| `Free` | 自由调整大小 |
| `Fixed` | 固定大小 |
| `AspectRatio` | 保持宽高比 |
---
## 🧩 完整示例
```csharp
public override void Initialize(HostBuilderContext context, IServiceCollection services)
{
// 天气组件
services.AddPluginDesktopComponent<WeatherWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "WeatherPlugin.Widget",
DisplayName = "天气",
IconKey = "Weather",
Category = "信息",
MinWidthCells = 4,
MinHeightCells = 3,
CornerRadiusPreset = PluginCornerRadiusPreset.Component,
ResizeMode = PluginDesktopComponentResizeMode.Free
});
// 时钟组件
services.AddPluginDesktopComponent<ClockWidget>(
new PluginDesktopComponentOptions
{
ComponentId = "ClockPlugin.Widget",
DisplayName = "时钟",
IconKey = "Clock",
Category = "工具",
MinWidthCells = 4,
MinHeightCells = 4,
CornerRadiusPreset = PluginCornerRadiusPreset.Component,
ResizeMode = PluginDesktopComponentResizeMode.AspectRatio
});
}
```
---
## 📚 参考资源
- [PluginDesktopComponentOptions 源码](../../LanMountainDesktop.PluginSdk/PluginDesktopComponentOptions.cs)
- [02-桌面组件系统](../02-核心概念与原理/02-桌面组件系统.md)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,147 @@
# 03-设置API详解
设置 API 允许插件添加配置页面和持久化用户设置。
---
## 🎯 API 概览
### 声明式设置
```csharp
services.AddPluginSettingsSection(
string sectionId,
string displayName,
Action<PluginSettingsSectionBuilder> configure,
string iconKey);
```
### 自定义设置页
```csharp
services.AddPluginSettingsSection<TPage>(
string sectionId,
string displayName,
string iconKey)
where TPage : SettingsPageBase;
```
---
## 📋 声明式设置详解
### 基本用法
```csharp
services.AddPluginSettingsSection(
"myplugin-settings",
"我的设置",
section => section
.AddToggle("enabled", "启用", defaultValue: true)
.AddText("name", "名称", defaultValue: ""),
iconKey: "Settings");
```
### 设置类型
#### Toggle开关
```csharp
.AddToggle(
key: "auto_update",
displayName: "自动更新",
defaultValue: true,
description: "启动时检查更新")
```
#### Text文本
```csharp
.AddText(
key: "api_key",
displayName: "API密钥",
defaultValue: "",
placeholder: "请输入",
isPassword: false)
```
#### Number数值
```csharp
.AddNumber(
key: "interval",
displayName: "刷新间隔",
defaultValue: 60,
minimum: 10,
maximum: 3600,
increment: 10)
```
#### Select选择
```csharp
.AddSelect(
key: "theme",
displayName: "主题",
choices: new[]
{
new SettingsOptionChoice("light", "浅色"),
new SettingsOptionChoice("dark", "深色")
},
defaultValue: "light")
```
#### Path路径
```csharp
.AddPath(
key: "save_path",
displayName: "保存路径",
defaultValue: "",
pathType: SettingsPathType.Folder,
dialogTitle: "选择文件夹")
```
---
## 🔧 读取和保存设置
### 使用 IPluginSettingsService
```csharp
public class MyService
{
private readonly IPluginSettingsService _settings;
public MyService(IPluginSettingsService settings)
{
_settings = settings;
// 读取
var value = _settings.GetValue<string>("key", "default");
// 保存
_settings.SetValue("key", "new value");
// 监听变化
_settings.SettingsChanged += (s, e) =>
{
if (e.Key == "key")
{
HandleChange(e.NewValue);
}
};
}
}
```
---
## 📚 参考资源
- [IPluginSettingsService 源码](../../LanMountainDesktop.PluginSdk/IPluginSettingsService.cs)
- [03-设置系统集成](../02-核心概念与原理/03-设置系统集成.md)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,106 @@
# 04-外观API详解
外观 API 提供圆角、主题等视觉相关的功能。
---
## 🎯 IPluginAppearanceContext
```csharp
public interface IPluginAppearanceContext
{
// 获取圆角值
CornerRadius ResolveCornerRadius(PluginCornerRadiusPreset preset);
// 获取带限制的圆角值
CornerRadius ResolveCornerRadius(
PluginCornerRadiusPreset preset,
CornerRadius? minimum,
CornerRadius? maximum);
// 获取缩放后的圆角值
CornerRadius ResolveScaledCornerRadius(
double baseRadius,
double? minimum,
double? maximum);
// 外观变化事件
event EventHandler? AppearanceChanged;
}
```
---
## 📐 圆角 API
### 获取圆角值
```csharp
public MyWidget(PluginDesktopComponentContext context)
{
// 使用预设
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
}
```
### 带限制的圆角
```csharp
var radius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component,
minimum: new CornerRadius(8),
maximum: new CornerRadius(24));
```
### 缩放圆角
```csharp
var radius = context.Appearance.ResolveScaledCornerRadius(
baseRadius: 16,
minimum: 8,
maximum: 32);
```
---
## 🎨 圆角预设
| 预设 | 值 | 用途 |
|-----|---|------|
| Micro | 6px | 微小元素 |
| Xs | 12px | 小元素 |
| Sm | 14px | 小卡片 |
| Md | 20px | 普通按钮 |
| Lg | 28px | 大面板 |
| Xl | 32px | 强调容器 |
| Island | 36px | 大型容器 |
| Component | 18px | 桌面组件 |
| Default | 自适应 | 自动计算 |
---
## 🔄 响应外观变化
```csharp
public MyWidget(PluginDesktopComponentContext context)
{
context.Appearance.AppearanceChanged += (_, _) =>
{
// 重新应用圆角
CornerRadius = context.Appearance.ResolveCornerRadius(
PluginCornerRadiusPreset.Component);
};
}
```
---
## 📚 参考资源
- [IPluginAppearanceContext 源码](../../LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs)
- [04-外观与主题系统](../02-核心概念与原理/04-外观与主题系统.md)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,73 @@
# 05-本地化支持
本地化 API 支持多语言资源管理。
---
## 🎯 资源文件
### 文件位置
```
Localization/
├── zh-CN.json # 简体中文
├── en-US.json # 英文
├── ja-JP.json # 日文
└── ko-KR.json # 韩文
```
### 资源格式
```json
{
"PluginName": "我的插件",
"Settings": {
"Title": "设置",
"Save": "保存"
},
"Messages": {
"Hello": "你好,{0}!",
"Error": "错误:{0}"
}
}
```
---
## 📝 使用本地化
### 注入 IStringLocalizer
```csharp
public class MyService
{
private readonly IStringLocalizer<MyService> _localizer;
public MyService(IStringLocalizer<MyService> localizer)
{
_localizer = localizer;
}
public void DoWork()
{
// 简单字符串
var name = _localizer["PluginName"];
// 带参数
var message = _localizer["Messages.Hello", "用户"];
// 嵌套键
var title = _localizer["Settings.Title"];
}
}
```
---
## 📚 参考资源
- [Microsoft.Extensions.Localization 文档](https://docs.microsoft.com/dotnet/api/microsoft.extensions.localization)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,215 @@
# 01-GitHub Actions入门
GitHub Actions 是自动化构建、测试和发布插件的强大工具。本文介绍如何为插件项目配置 CI/CD 流程。
---
## 🎯 什么是 GitHub Actions
GitHub Actions 是 GitHub 提供的持续集成/持续部署CI/CD服务可以
- ✅ 自动构建插件
- ✅ 运行单元测试
- ✅ 打包 .laapp 文件
- ✅ 自动发布到 GitHub Releases
---
## 📁 工作流文件位置
```
.github/workflows/
├── build.yml # 构建工作流
├── release.yml # 发布工作流
└── code-quality.yml # 代码质量检查
```
---
## 🚀 基础工作流示例
### 最简单的构建工作流
```yaml
# .github/workflows/build.yml
name: Build Plugin
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
build:
runs-on: windows-latest
steps:
# 1. 检出代码
- name: Checkout
uses: actions/checkout@v4
# 2. 设置 .NET
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
# 3. 还原依赖
- name: Restore
run: dotnet restore
# 4. 构建
- name: Build
run: dotnet build --configuration Release --no-restore
# 5. 上传构建产物
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: plugin-package
path: bin/Release/net10.0/*.laapp
```
---
## 📋 工作流详解
### 触发条件on
```yaml
on:
# 推送到指定分支时触发
push:
branches: [main, master, develop]
# 创建 Pull Request 时触发
pull_request:
branches: [main, master]
# 手动触发
workflow_dispatch:
# 定时触发每天凌晨2点
schedule:
- cron: '0 2 * * *'
# 创建标签时触发(用于发布)
push:
tags:
- 'v*'
```
### 运行环境runs-on
```yaml
jobs:
build:
runs-on: windows-latest # Windows 环境
# 或
runs-on: ubuntu-latest # Linux 环境
# 或
runs-on: macos-latest # macOS 环境
```
### 矩阵构建(多平台)
```yaml
jobs:
build:
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
dotnet: ['10.0.x']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet }}
```
---
## 🔧 常用 Actions
### 检出代码
```yaml
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史(用于生成版本号)
```
### 设置 .NET
```yaml
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
```
### 上传产物
```yaml
- uses: actions/upload-artifact@v4
with:
name: plugin-package
path: bin/Release/net10.0/*.laapp
retention-days: 30 # 保留30天
```
### 下载产物
```yaml
- uses: actions/download-artifact@v4
with:
name: plugin-package
path: ./artifacts
```
---
## 💡 最佳实践
### 1. 缓存依赖
```yaml
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
```
### 2. 使用语义化版本
```yaml
- name: Get Version
id: version
run: |
VERSION=$(echo ${GITHUB_REF#refs/tags/} | sed 's/^v//')
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
```
### 3. 条件执行
```yaml
- name: Deploy
if: github.ref == 'refs/heads/main' # 只在 main 分支执行
run: echo "Deploying..."
```
---
## 🎯 下一步
学习自动打包配置:
👉 **[02-配置自动构建](02-配置自动构建.md)**
---
*最后更新2026年4月*

View File

@@ -0,0 +1,130 @@
# 02-配置自动构建
配置 GitHub Actions 自动构建插件项目。
---
## 🎯 完整构建工作流
```yaml
# .github/workflows/build.yml
name: Build
on:
push:
branches: [main, master]
paths-ignore:
- '**.md'
- '.gitignore'
pull_request:
branches: [main, master]
env:
DOTNET_VERSION: '10.0.x'
CONFIGURATION: 'Release'
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration ${{ env.CONFIGURATION }} --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: plugin-${{ github.run_number }}
path: bin/${{ env.CONFIGURATION }}/net10.0/*.laapp
```
---
## 🔧 关键配置说明
### 路径过滤
```yaml
on:
push:
paths-ignore:
- '**.md' # 忽略文档修改
- '.gitignore' # 忽略 gitignore 修改
- 'docs/**' # 忽略 docs 文件夹
```
### 环境变量
```yaml
env:
DOTNET_VERSION: '10.0.x'
CONFIGURATION: 'Release'
PLUGIN_NAME: 'MyPlugin'
```
### 构建步骤
```yaml
steps:
# 1. 检出
- uses: actions/checkout@v4
# 2. 设置 .NET
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# 3. 缓存
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ hashFiles('**/*.csproj') }}
# 4. 还原
- run: dotnet restore
# 5. 构建
- run: dotnet build -c ${{ env.CONFIGURATION }} --no-restore
# 6. 测试
- run: dotnet test --no-build
# 7. 上传
- uses: actions/upload-artifact@v4
with:
name: plugin
path: bin/Release/net10.0/*.laapp
```
---
## 📚 参考资源
- [GitHub Actions 文档](https://docs.github.com/actions)
- [.NET CI/CD 指南](https://docs.microsoft.com/dotnet/devops/github-actions-overview)
---
*最后更新2026年4月*

View File

@@ -0,0 +1,156 @@
# 03-自动打包与发布
配置 GitHub Actions 自动打包 .laapp 并发布到 GitHub Releases。
---
## 🎯 发布工作流
```yaml
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
env:
DOTNET_VERSION: '10.0.x'
jobs:
build-and-release:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Get Version
id: version
run: |
$version = $env:GITHUB_REF -replace 'refs/tags/v', ''
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Package
run: |
$version = "${{ steps.version.outputs.VERSION }}"
Rename-Item -Path "bin/Release/net10.0/MyPlugin.laapp" -NewName "MyPlugin-$version.laapp"
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: bin/Release/net10.0/*.laapp
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
---
## 📋 发布流程
### 1. 创建标签
```bash
# 创建版本标签
git tag -a v1.0.0 -m "Release version 1.0.0"
# 推送标签到 GitHub
git push origin v1.0.0
```
### 2. 自动触发
推送标签后GitHub Actions 会自动:
1. 检出代码
2. 构建项目
3. 打包 .laapp
4. 创建 Release
5. 上传产物
### 3. 查看 Release
在 GitHub 仓库页面 → Releases 查看自动创建的发布。
---
## 🔧 高级配置
### 预发布版本
```yaml
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: bin/Release/net10.0/*.laapp
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
```
### 生成变更日志
```yaml
- name: Generate Changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
with:
configuration: .github/changelog-config.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
files: bin/Release/net10.0/*.laapp
```
---
## 💡 最佳实践
### 版本号管理
```yaml
- name: Update Version
run: |
$version = "${{ github.ref_name }}" -replace '^v', ''
$json = Get-Content plugin.json | ConvertFrom-Json
$json.version = $version
$json | ConvertTo-Json | Set-Content plugin.json
```
### 多文件发布
```yaml
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
bin/Release/net10.0/*.laapp
README.md
LICENSE
```
---
## 🎯 下一步
学习多平台构建:
👉 **[04-多平台构建策略](04-多平台构建策略.md)**
---
*最后更新2026年4月*

View File

@@ -0,0 +1,92 @@
# 01-插件打包规范
了解 .laapp 包的规范和结构,确保插件能正确安装和运行。
---
## 📦 .laapp 文件格式
`.laapp` 是阑山桌面的插件包格式,本质上是一个 **ZIP 压缩包**
### 文件结构
```
MyPlugin.laapp
├── plugin.json # 插件清单(必需)
├── MyPlugin.dll # 主程序集(必需)
├── Localization/ # 本地化文件夹
│ ├── zh-CN.json
│ └── en-US.json
└── *.dll # 依赖项
```
---
## 📋 plugin.json 规范
### 必需字段
```json
{
"id": "com.example.myplugin",
"name": "我的插件",
"description": "插件描述",
"author": "作者名",
"version": "1.0.0",
"apiVersion": "5.0.0",
"entranceAssembly": "MyPlugin.dll",
"sharedContracts": []
}
```
### 字段验证规则
| 字段 | 规则 |
|-----|------|
| `id` | 小写字母、数字、点号,反向域名格式 |
| `version` | 语义化版本x.y.z |
| `apiVersion` | 必须与 SDK 版本兼容 |
| `entranceAssembly` | 必须与 DLL 文件名一致 |
---
## 🔨 构建配置
### .csproj 关键配置
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LanMountainDesktop.PluginSdk" Version="5.0.0" />
</ItemGroup>
<!-- 确保资源文件复制到输出目录 -->
<ItemGroup>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Localization\**\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
```
---
## ✅ 打包检查清单
- [ ] `plugin.json` 格式正确
- [ ] `id` 全局唯一
- [ ] `apiVersion` 与 SDK 版本匹配
- [ ] DLL 文件名与 `entranceAssembly` 一致
- [ ] 所有依赖项已包含
- [ ] 本地化文件完整
---
*最后更新2026年4月*

View File

@@ -0,0 +1,75 @@
# 02-版本管理策略
合理的版本管理是插件维护的基础。
---
## 🎯 语义化版本SemVer
版本格式:`主版本.次版本.修订号`
| 版本变化 | 说明 | 示例 |
|---------|------|------|
| 主版本Major | 破坏性变更 | 1.0.0 → 2.0.0 |
| 次版本Minor | 新功能,向后兼容 | 1.0.0 → 1.1.0 |
| 修订号Patch | Bug 修复 | 1.0.0 → 1.0.1 |
---
## 📋 版本示例
| 版本 | 含义 |
|-----|------|
| `1.0.0` | 首个正式版 |
| `1.1.0` | 新增功能 |
| `1.1.1` | 修复 Bug |
| `2.0.0-beta` | 2.0 测试版 |
| `2.0.0-rc1` | 2.0 候选版 |
---
## 🔄 版本更新流程
### 1. 更新版本号
```json
// plugin.json
{
"version": "1.1.0"
}
```
### 2. 更新 CHANGELOG.md
```markdown
## [1.1.0] - 2024-04-13
### 新增
- 添加天气预警功能
- 支持多城市管理
### 修复
- 修复定位失败问题
```
### 3. 创建 Git 标签
```bash
git add .
git commit -m "Release v1.1.0"
git tag -a v1.1.0 -m "Release version 1.1.0"
git push origin main --tags
```
---
## 💡 最佳实践
- 使用 GitHub Releases 管理版本
- 每个版本都写更新日志
- 测试版使用 `-beta``-alpha` 后缀
- 保持向后兼容,避免频繁主版本升级
---
*最后更新2026年4月*

View File

@@ -0,0 +1,71 @@
# 03-发布到插件市场
将插件发布到阑山桌面插件市场,让更多用户使用。
---
## 🎯 发布流程
### 1. 准备材料
- 插件包(.laapp
- 插件图标256x256 PNG
- 截图(至少 1 张)
- 详细描述
- 更新日志
### 2. 提交审核
1. 访问阑山桌面开发者门户
2. 登录开发者账号
3. 点击「提交新插件」
4. 填写插件信息
5. 上传 .laapp 文件
6. 提交审核
### 3. 审核标准
| 检查项 | 要求 |
|-------|------|
| 功能完整性 | 插件能正常运行 |
| 安全性 | 无恶意代码 |
| 用户体验 | 界面美观,操作流畅 |
| 文档完整 | 有基本使用说明 |
---
## 📋 元数据要求
### 必需信息
```json
{
"id": "com.example.plugin",
"name": "插件名称",
"description": "简短描述50字以内",
"author": "作者名",
"version": "1.0.0",
"tags": ["工具", "天气"],
"website": "https://github.com/..."
}
```
### 图标规范
- 格式PNG
- 尺寸256x256 像素
- 背景:透明
- 风格:与阑山桌面一致
---
## 🚀 发布后
- 关注用户反馈
- 及时修复 Bug
- 定期更新功能
- 维护更新日志
---
*最后更新2026年4月*

View File

@@ -0,0 +1,82 @@
# 04-更新与维护
插件发布后的持续维护和更新策略。
---
## 🔄 更新策略
### 热修复Hotfix
发现严重 Bug 时立即发布:
```bash
# 1. 修复 Bug
git checkout -b hotfix/critical-bug
# ... 修复代码 ...
# 2. 更新版本号(修订号+1
# plugin.json: "version": "1.0.1"
# 3. 提交并发布
git commit -m "Fix critical bug"
git tag -a v1.0.1 -m "Hotfix v1.0.1"
git push origin main --tags
```
### 功能更新
```bash
# 1. 开发新功能
git checkout -b feature/new-feature
# 2. 完成功能后合并到 main
# 3. 更新版本号(次版本+1
# plugin.json: "version": "1.1.0"
# 4. 发布
git tag -a v1.1.0 -m "Release v1.1.0"
git push origin main --tags
```
---
## 📊 维护清单
### 日常维护
- [ ] 监控用户反馈
- [ ] 查看崩溃报告
- [ ] 回复用户问题
- [ ] 更新依赖包
### 定期维护
- [ ] 每季度检查 SDK 更新
- [ ] 每年评估功能需求
- [ ] 定期更新文档
---
## 🐛 Bug 处理流程
1. **收集信息** - 复现步骤、环境信息
2. **定位问题** - 本地调试复现
3. **修复验证** - 修复后充分测试
4. **发布更新** - 按热修复流程发布
5. **通知用户** - 在 Release 中说明
---
## 💡 最佳实践
- 保持向后兼容
- 及时响应用户反馈
- 定期发布小更新
- 维护清晰的更新日志
- 废弃功能提前通知
---
*最后更新2026年4月*

36
docs/archive/README.md Normal file
View File

@@ -0,0 +1,36 @@
# 旧版文档归档
本目录包含阑山桌面项目的历史文档。这些文档已被新的文档体系替代,但保留在此以供参考。
## 归档时间
2026年6月8日
## 归档内容
- **原有插件开发文档** (`Plugins develop/`) - SDK v5 之前的插件开发指南
- **原有技术文档** - 包括架构、构建、更新系统等文档
- **AI 辅助文档** (`ai/`) - AI 生成的代码库地图和工作流程
- **自动提交文档** (`auto_commit_md/`) - 自动生成的提交记录文档
- **Superpowers 计划** (`superpowers/`) - 早期功能规划文档
## 新文档位置
新的文档体系位于 `docs/` 根目录,按照以下结构组织:
```
docs/
├── 00-快速开始/ # 项目概览、快速安装
├── 01-插件开发/ # 完整插件开发指南
├── 02-AirApp开发/ # Air APP 应用开发
├── 03-组件设计规范/ # 桌面组件设计系统
├── 04-架构与实现/ # 技术架构文档
├── 05-更新与发布/ # 更新系统和发布流程
└── archive/ # 本归档目录
```
## 注意事项
- 旧文档中的 API 和示例代码可能已过时
- 请优先参考新文档体系
- 如需查看历史实现细节,可参考此归档

View File

@@ -0,0 +1,20 @@
# Runtime Packaging
## Windows
- Windows installers do not bundle the .NET shared runtime.
- `LanMountainDesktop.Launcher.exe` is the package-root bootstrapper and remains Native AOT/self-contained.
- `LanMountainDesktop.AirAppRuntime.exe` is a package-root framework-dependent JIT process. Launcher pre-starts it, and Host can start it as a direct-run fallback.
- `LanMountainDesktop.exe` and `LanMountainDesktop.AirAppHost.exe` are framework-dependent, RID-specific apps under `app-<version>/`.
- Inno Setup downloads and silently installs the matching .NET 10 Desktop Runtime before continuing:
- x64 installer: `https://aka.ms/dotnet/10.0/windowsdesktop-runtime-win-x64.exe`
- x86 installer: `https://aka.ms/dotnet/10.0/windowsdesktop-runtime-win-x86.exe`
- Launcher runtime probing validates the architecture-matched `Microsoft.NETCore.App 10.*` shared framework before starting framework-dependent processes.
If the launcher returns `dotnet_runtime_missing`, verify the runtime architecture:
```powershell
dotnet --list-runtimes
Test-Path "C:\Program Files\dotnet\shared\Microsoft.NETCore.App"
Test-Path "C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App"
```

76
docs/archive/SPECS.md Normal file
View File

@@ -0,0 +1,76 @@
# 规格文档说明 / Specs
## 中文
### 目的
`.trae/specs/` 用来存放“一个需求从意图到落地”的协作文档,而不是长期产品说明。它适合记录功能变更、交互改造、重要修复和跨模块调整。
### 目录结构
每个功能目录建议使用:
```text
.trae/specs/<feature-name>/
spec.md
tasks.md
checklist.md
```
### 每个文件的职责
#### `spec.md`
用于描述这次变更的意图和行为要求,建议包含:
- `Why`:为什么要做
- `What Changes`:会改什么
- `Impact`:影响哪些规范或代码区域
- Requirements / Scenarios可验证的行为要求
#### `tasks.md`
用于把实现拆成可执行任务,建议包含:
- 分阶段任务或模块任务
- 依赖关系
- 可并行项
- 完成状态
#### `checklist.md`
用于验收与回归检查,建议包含:
- 关键 UI 或行为检查点
- 构建、运行、测试检查点
- 手工验证项
### 什么时候新建 spec
- 新增功能
- 已有功能行为发生变化
- 设置页、主界面、组件系统出现结构性调整
- 插件系统、共享契约、SDK 接入方式发生变化
### 什么时候只更新现有 spec
- 同一 feature 的后续迭代仍属于原目标范围
- 原 spec 仍是当前实现的权威描述
- 只是补充场景、任务拆解或验收项
### 什么时候可以不写 spec
- 纯拼写修复
- 纯内部重构且不改变行为
- 只改注释、日志、文档索引等非行为项
### 与其他文档的关系
- 长期产品说明看 `docs/PRODUCT.md`
- 长期架构说明看 `docs/ARCHITECTURE.md`
- 开发运行方式看 `docs/DEVELOPMENT.md`
- feature 级变更过程看 `.trae/specs/`
## English
Use `.trae/specs/` for feature-level change tracking, not for long-lived product or architecture documentation. `spec.md` defines intent and requirements, `tasks.md` breaks implementation into actionable work, and `checklist.md` captures validation and regression checks.

View File

@@ -0,0 +1,681 @@
# 鏁呴殰鎺掗櫎鎸囧崡
> LanMountainDesktop 甯歌<E794AF><EFBFBD><E99782>鍜岃В鍐虫柟妗<E69F9F>
## 鐩<>
- [鏋勫缓闂<EFBFBD><EFBFBD>](#鏋勫缓闂<E7BC93><E99782>)
- [杩愯<E69DA9>鏃堕棶棰榏(#杩愯<EFBFBD>鏃堕棶棰<EFBFBD>)
- [Launcher 闂<><E99782>](#launcher-闂<><E99782>)
- [鏇存柊闂<EFBFBD><EFBFBD>](#鏇存柊闂<E69F8A><E99782>)
- [鎻掍欢闂<EFBFBD><EFBFBD>](#鎻掍欢闂<E6ACA2><E99782>)
- [鎬ц兘闂<EFBFBD><EFBFBD>](#鎬ц兘闂<E58598><E99782>)
- [骞冲彴鐗瑰畾闂<EFBFBD><EFBFBD>](#骞冲彴鐗瑰畾闂<E795BE><E99782>)
## 鏋勫缓闂<E7BC93><E99782>
### 闂<><E99782>: 缂栬瘧閿欒<E996BF> - 鎵句笉鍒<E7AC89> Windows.Win32 鍛藉悕绌洪棿
**鐥囩姸:**
```
error CS0246: The type or namespace name 'Windows' could not be found
```
**鍘熷洜:** CsWin32 灏氭湭鐢熸垚 P/Invoke 浠g爜
**瑙喅鏂规<E98F82>:**
```bash
# 娓呯悊骞堕噸鏂版瀯寤<E780AF>
dotnet clean
dotnet restore
dotnet build
```
棣栨<EFBFBD>鏋勫缓鏃<EFBFBD> CsWin32 浼氳嚜鍔ㄧ敓鎴愪唬鐮<E594AC>,绗<>簩娆℃瀯寤哄簲璇ユ垚鍔熴€<E786B4>
---
### 闂<><E99782>: NuGet 鍖呰繕鍘熷け璐<E38191>
**鐥囩姸:**
```
error NU1102: Unable to find package 'XXX' with version (>= X.X.X)
```
**瑙喅鏂规<E98F82>:**
```bash
# 娓呯悊 NuGet 缂撳瓨
dotnet nuget locals all --clear
# 寮哄埗杩樺師
dotnet restore --force
# 閲嶆柊鏋勫缓
dotnet build
```
---
### 闂<><E99782>: 鏋勫缓鏃舵彁绀<E5BD81> SDK 鐗堟湰涓嶅尮閰<E5B0AE>
**鐥囩姸:**
```
error NETSDK1045: The current .NET SDK does not support targeting .NET 10.0
```
**瑙喅鏂规<E98F82>:**
```bash
# 妫€鏌ュ綋鍓<E7B68B> SDK 鐗堟湰
dotnet --version
# 搴旇<E690B4>鏄剧ず 10.x.x
# 濡傛灉涓嶆槸,璇峰畨瑁<E795A8> .NET 10 SDK
```
涓嬭浇鍦板潃: https://dotnet.microsoft.com/download/dotnet/10.0
---
### 闂<><E99782>: Avalonia 璁捐<E79281>鍣ㄦ棤娉曞姞杞<E5A79E>
**鐥囩姸:** XAML 棰勮<E6A3B0>鏄剧ず閿欒<E996BF>鎴栫┖鐧<E29496>
**瑙喅鏂规<E98F82>:**
1. 閲嶅惎 IDE
2. 娓呯悊骞堕噸鏂版瀯寤洪」鐩<E3808D>
3. 妫€鏌<E282AC> Avalonia 鐗堟湰鏄<E6B9B0>惁涓€鑷<E282AC>
```bash
dotnet clean
dotnet build
```
## 杩愯<E69DA9>鏃堕棶棰<E6A3B6>
### 闂<><E99782>: 搴旂敤鍚<E695A4>姩鍚庣珛鍗冲穿婧<E7A9BF>
**璇婃柇姝ラ<E5A79D>:**
1. **妫€鏌ユ棩蹇楁枃浠<E69E83>:**
```
Windows: %LOCALAPPDATA%\LanMountainDesktop\logs\
Linux: ~/.local/share/LanMountainDesktop/logs/
macOS: ~/Library/Application Support/LanMountainDesktop/logs/
```
2. **浠ヨ皟璇曟ā寮忚繍琛<E7B98D>:**
```bash
dotnet run --project LanMountainDesktop/LanMountainDesktop.csproj
```
3. **妫€鏌ヤ緷璧<E7B7B7>:**
```bash
# Windows: 纭<>繚瀹夎<E780B9><EFBFBD> .NET 10 Desktop Runtime
# Linux: 纭<>繚瀹夎<E780B9>浜嗗繀瑕佺殑鍥惧舰搴<E888B0>
```
---
### 闂<><E99782>: 绐楀彛鏃犳硶鏄剧ず鎴栭粦灞<E7B2A6>
**鍙<>兘鍘熷洜:**
- 鏄惧崱椹卞姩闂<E5A7A9><E99782>
- 娓叉煋妯″紡涓嶅吋瀹<E5908B>
**瑙喅鏂规<E98F82>:**
1. **鍒囨崲娓叉煋妯″紡** (缂栬緫閰嶇疆鏂囦欢):
```json
{
"Win32RenderingMode": 1 // 灏濊瘯涓嶅悓鐨勫€<E58BAB>: 0, 1, 2, 3, 4
}
```
2. **绂佺敤纭<E695A4>欢鍔犻€<E78ABB>:**
```bash
# 璁剧疆鐜<E79686><E9909C>鍙橀噺
set AVALONIA_RENDERING_MODE=Software
```
---
### 闂<><E99782>: 鍗曞疄渚嬮攣瀹氬け璐<E38191>
**鐥囩姸:** 鎻愮ず"搴旂敤宸插湪杩愯<E69DA9>"浣嗗疄闄呮病鏈<E79785>
**瑙喅鏂规<E98F82>:**
```bash
# Windows
taskkill /F /IM LanMountainDesktop.exe
# Linux/macOS
pkill -9 LanMountainDesktop
```
濡傛灉闂<EFBFBD><EFBFBD>鎸佺画,鍒犻櫎閿佹枃浠<E69E83>:
```
Windows: %TEMP%\LanMountainDesktop.lock
Linux: /tmp/LanMountainDesktop.lock
macOS: /tmp/LanMountainDesktop.lock
```
---
### 闂<><E99782>: 璁剧疆鏃犳硶淇濆瓨
**鐥囩姸:**<>敼璁剧疆鍚庨噸鍚<E599B8>簲鐢<E7B0B2>,璁剧疆鎭㈠<E98EAD>榛樿<E6A69B>
**璇婃柇:**
```bash
# 妫€鏌ヨ<E98F8C><EFBFBD>枃浠舵槸鍚﹀瓨鍦<E793A8>
# Windows: %LOCALAPPDATA%\LanMountainDesktop\settings.json
# Linux: ~/.local/share/LanMountainDesktop/settings.json
# macOS: ~/Library/Application Support/LanMountainDesktop/settings.json
```
**瑙喅鏂规<E98F82>:**
1. 妫€鏌ユ枃浠舵潈闄<E6BD88>
2. 妫€鏌ョ<E98F8C>鐩樼┖闂<E29496>
3. 鍒犻櫎鎹熷潖鐨勮<E990A8><EFBFBD>枃浠<E69E83> (浼氶噸缃<E599B8>负榛樿<E6A69B>)
## Launcher 闂<><E99782>
### 闂<><E99782>: Launcher 鎵句笉鍒颁富绋嬪簭
**鐥囩姸:**
```
鎵句笉鍒版湁鏁堢殑 LanMountainDesktop 鐗堟湰锛屽彲鑳芥槸瀹夎<E780B9>宸叉崯鍧忋€<E5BF8B>
```
**璇婃柇:**
```bash
# 妫€鏌ョ洰褰曠粨鏋<E7B2A8>
ls "C:\Program Files\LanMountainDesktop\"
# 搴旇<E690B4>鐪嬪埌:
# - LanMountainDesktop.Launcher.exe
# - app-{version}/
```
**瑙喅鏂规<E98F82>:**
1. **妫€鏌<E282AC> app-* 鐩<>綍鏄<E7B68D>惁瀛樺湪:**
```bash
ls "C:\Program Files\LanMountainDesktop\app-*"
```
2. **妫€鏌ヤ富绋嬪簭鏄<E7B0AD>惁瀛樺湪:**
```bash
ls "C:\Program Files\LanMountainDesktop\app-{version}\LanMountainDesktop.exe"
```
3. **閲嶆柊瀹夎<E780B9>搴旂敤**
---
### 闂<><E99782>: OOBE 绐楀彛閲嶅<E996B2>鍑虹幇
**鐥囩姸:** 姣忔<E5A7A3><EFBFBD>姩閮芥樉绀烘<E7BB80>杩庨〉闈<E38089>
**鍘熷洜:** OOBE 瀹屾垚鏍囪<E98F8D>鏂囦欢涓㈠け鎴栨棤娉曞垱寤<E59EB1>
**瑙喅鏂规<E98F82>:**
```bash
# 鎵嬪姩鍒涘缓鏍囪<E98F8D>鏂囦欢
# Windows:
New-Item -ItemType File -Path "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\state\first_run_completed"
# Linux/macOS:
mkdir -p ~/.local/share/LanMountainDesktop/.launcher/state
touch ~/.local/share/LanMountainDesktop/.launcher/state/first_run_completed
```
---
### 闂<><E99782>: Splash 绐楀彛鍗綇涓嶆秷澶<E7A7B7>
**鐥囩姸:**<>姩鍔ㄧ敾涓€鐩存樉绀<E6A889>,涓荤▼搴忔棤娉曞惎鍔<E6838E>
**璇婃柇:**
```bash
# 妫€鏌ヤ富绋嬪簭鏄<E7B0AD>惁鍚<E68381>
# Windows:
tasklist | findstr LanMountainDesktop
# Linux/macOS:
ps aux | grep LanMountainDesktop
```
**瑙喅鏂规<E98F82>:**
1. 寮哄埗鍏抽棴 Launcher
2. 鐩存帴杩愯<E69DA9>涓荤▼搴忔祴璇<E7A5B4>:
```bash
"C:\Program Files\LanMountainDesktop\app-{version}\LanMountainDesktop.exe"
```
3. 妫€鏌ユ棩蹇楁枃浠<E69E83>
## 鏇存柊闂<E69F8A><E99782>
### 闂<><E99782>: 鏇存柊涓嬭浇澶辫触
**鐥囩姸:**
```
Failed to download update: The remote server returned an error
```
**鍙<>兘鍘熷洜:**
- 缃戠粶杩炴帴闂<E5B8B4><E99782>
- GitHub API 闄愭祦
-悊璁剧疆闂<E79686><E99782>
**瑙喅鏂规<E98F82>:**
1. **妫€鏌ョ綉缁滆繛鎺<E7B99B>:**
```bash
# 娴嬭瘯 GitHub 杩炴帴
curl https://api.github.com/repos/YourOrg/LanMountainDesktop/releases/latest
```
2. **閰嶇疆浠g悊** (濡傛灉闇€瑕<E282AC>):
```bash
# 璁剧疆鐜<E79686><E9909C>鍙橀噺
set HTTP_PROXY=http://proxy.example.com:8080
set HTTPS_PROXY=http://proxy.example.com:8080
```
3. **鎵嬪姩涓嬭浇鏇存柊:**
- 璁块棶 GitHub Releases 椤甸潰
- 涓嬭浇瀹夎<E780B9><EFBFBD>
- 閲嶆柊瀹夎<E780B9>
---
### 闂<><E99782>: 鏇存柊绛惧悕楠岃瘉澶辫触
**鐥囩姸:**
```
Signature verification failed
```
**鍘熷洜:**
- 鏂囦欢鎹熷潖
-<>挜涓嶅尮閰<E5B0AE>
- 鏂囦欢琚<E6ACA2><E7909A><EFBFBD>
**瑙喅鏂规<E98F82>:**
1. **鍒犻櫎鎹熷潖鐨勬洿鏂版枃浠<E69E83>:**
```bash
# Windows:
Remove-Item "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\update\incoming\*"
# Linux/macOS:
rm -rf ~/.local/share/LanMountainDesktop/.launcher/update/incoming/*
```
2. **閲嶆柊涓嬭浇鏇存柊**
3. **濡傛灉闂<E78189><E99782>鎸佺画,閲嶆柊瀹夎<E780B9>搴旂敤**
---
### 闂<><E99782>: 鏇存柊鍚庡簲鐢ㄦ棤娉曞惎鍔<E6838E>
**鐥囩姸:** 鏇存柊瀹屾垚鍚<E59E9A>,搴旂敤鍚<E695A4>姩澶辫触鎴栧穿婧<E7A9BF>
**瑙喅鏂规<E98F82>:**
1. **鐗堟湰鍥為€€:**
```bash
Use Host Settings > Update > Rollback
```
2. **妫€鏌ユ洿鏂板揩鐓<E68FA9>:**
```bash
# Windows:
ls "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\snapshots\"
# 鏌ョ湅蹇<E6B985>収鍐呭<E98D90>
cat "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\snapshots\{snapshot-id}.json"
```
3. **鎵嬪姩鍒囨崲鐗堟湰:**
```bash
# 鍒犻櫎鏂扮増鏈<E5A297>殑 .current 鏍囪<E98F8D>
Remove-Item "C:\Program Files\LanMountainDesktop\app-{new}\\.current"
# 娣诲姞 .current 鍒版棫鐗堟湰
New-Item -ItemType File -Path "C:\Program Files\LanMountainDesktop\app-{old}\\.current"
```
---
### 闂<><E99782>: 澧為噺鏇存柊鏂囦欢鍝堝笇涓嶅尮閰<E5B0AE>
**鐥囩姸:**
```
File hash mismatch for 'XXX.dll'
```
**鍘熷洜:**
- 鏂囦欢涓嬭浇涓嶅畬鏁<E795AC>
- 鏂囦欢鎹熷潖
**瑙喅鏂规<E98F82>:**
1. **鍒犻櫎閮ㄥ垎涓嬭浇鐨勬洿鏂<E6B4BF>:**
```bash
# 鍒犻櫎鏍囪<E98F8D><EFBFBD> .partial 鐨勭洰褰<E6B4B0>
Remove-Item "C:\Program Files\LanMountainDesktop\app-*" -Recurse -Force -Include *.partial
```
2. **娓呯悊鏇存柊缂撳瓨:**
```bash
Remove-Item "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\update\incoming\*"
```
3. **閲嶆柊涓嬭浇鏇存柊**
## 鎻掍欢闂<E6ACA2><E99782>
### 闂<><E99782>: 鎻掍欢鏃犳硶鍔犺浇
**鐥囩姸:** 鎻掍欢鍒楄〃涓<E38083>湅涓嶅埌宸插畨瑁呯殑鎻掍欢
**璇婃柇:**
```bash
# 妫€鏌ユ彃浠剁洰褰<E6B4B0>
ls "C:\Program Files\LanMountainDesktop\plugins\"
# 妫€鏌ユ彃浠舵竻鍗<E7ABBB>
cat "C:\Program Files\LanMountainDesktop\plugins\{plugin-id}\plugin.json"
```
**瑙喅鏂规<E98F82>:**
1. **妫€鏌ユ彃浠跺吋瀹规€<E8A784>:**
- 鎻掍欢 SDK 鐗堟湰鏄<E6B9B0>惁鍖归厤
- 鎻掍欢鏄<E6ACA2>惁鏀<E68381>寔褰撳墠骞冲彴
2. **閲嶆柊瀹夎<E780B9>鎻掍欢:**
```bash
LanMountainDesktop.Launcher.exe plugin install <path-to-plugin.laapp>
```
3. **妫€鏌ユ棩蹇楁枃浠<E69E83>** 鏌ョ湅鎻掍欢鍔犺浇閿欒<E996BF>
---
### 闂<><E99782>: 鎻掍欢瀹夎<E780B9>澶辫触
**鐥囩姸:**
```
Failed to install plugin: Invalid package format
```
**鍙<>兘鍘熷洜:**
- `.laapp` 鏂囦欢鎹熷潖
- 鎻掍欢鍖呮牸寮忎笉姝g‘
- 鏉冮檺涓嶈冻
**瑙喅鏂规<E98F82>:**
1. **楠岃瘉鎻掍欢鍖<E6ACA2>:**
```bash
# .laapp 瀹為檯涓婃槸 ZIP 鏂囦欢
unzip -t plugin.laapp
```
2. **妫€鏌ユ潈闄<E6BD88>:**
```bash
# 浠ョ<E6B5A0>鐞嗗憳韬<E686B3>唤杩愯<E69DA9> Launcher
```
3. **鎵嬪姩瑙帇瀹夎<E780B9>:**
```bash
# 瑙帇鍒版彃浠剁洰褰<E6B4B0>
unzip plugin.laapp -d "C:\Program Files\LanMountainDesktop\plugins\{plugin-id}"
```
---
### 闂<><E99782>: 鎻掍欢鏇存柊澶辫触
**鐥囩姸:** 鎻掍欢鍗囩骇闃熷垪澶勭悊澶辫触
**瑙喅鏂规<E98F82>:**
1. **娓呯悊鍗囩骇闃熷垪:**
```bash
Remove-Item "$env:LOCALAPPDATA\LanMountainDesktop\.launcher\plugin-upgrades\*"
```
2. **鎵嬪姩鏇存柊鎻掍欢:**
- 鍗歌浇鏃х増鏈<E5A297>
- 瀹夎<E780B9>鏂扮増鏈<E5A297>
## 鎬ц兘闂<E58598><E99782>
### 闂<><E99782>: CPU 鍗犵敤杩囬珮
**鍙<>兘鍘熷洜:**
- 娓叉煋妯″紡涓嶅綋
- 缁勪欢鏇存柊棰戠巼杩囬珮
- 鍐呭瓨娉勬紡
**璇婃柇:**
```bash
# Windows: 浣跨敤浠诲姟绠悊鍣ㄦ煡鐪嬭<E990AA>缁嗕俊鎭<E4BF8A>
# Linux: top 鎴<> htop
# macOS: Activity Monitor
```
**瑙喅鏂规<E98F82>:**
1. **鍒囨崲娓叉煋妯″紡** (鍙傝<E98D99>"绐楀彛鏃犳硶鏄剧ず"閮ㄥ垎)
2. **绂佺敤涓嶅繀瑕佺殑缁勪欢:**
- 鍑忓皯妗岄潰缁勪欢鏁伴噺
- 闄嶄綆缁勪欢鏇存柊棰戠巼
3. **妫€鏌ユ槸鍚︽湁姝诲惊鐜<E6838A>垨璧勬簮娉勬紡**
---
### 闂<><E99782>: 鍐呭瓨鍗犵敤杩囬珮
**璇婃柇:**
```bash
# 妫€鏌ュ唴瀛樹娇鐢ㄦ儏鍐<E5848F>
# Windows: 浠诲姟绠悊鍣<E6828A>
# Linux: free -h
# macOS: Activity Monitor
```
**瑙喅鏂规<E98F82>:**
1. **閲嶅惎搴旂敤**
2. **鍑忓皯缁勪欢鏁伴噺**
3. **妫€鏌ユ彃浠舵槸鍚︽湁鍐呭瓨娉勬紡**
4. **鏇存柊鍒版渶鏂扮増鏈<E5A297>** (鍙<>兘鍖呭惈鍐呭瓨浼樺寲)
---
### 闂<><E99782>: 鍚<>姩閫熷害鎱<E5AEB3>
**鍙<>兘鍘熷洜:**
- 鎻掍欢杩囧<E69DA9>
- 纾佺洏 I/O 鎱<>
- 棣栨<E6A3A3><EFBFBD>姩闇€瑕佸垵濮嬪寲
**瑙喅鏂规<E98F82>:**
1. **绂佺敤涓嶅繀瑕佺殑鎻掍欢**
2. **浣跨敤 SSD**
3. **娓呯悊缂撳瓨:**
```bash
Remove-Item "$env:LOCALAPPDATA\LanMountainDesktop\cache\*" -Recurse
```
## 骞冲彴鐗瑰畾闂<E795BE><E99782>
### Windows
#### 闂<><E99782>: WebView2 缂哄け
**鐥囩姸:**
```
Microsoft Edge WebView2 Runtime is required
```
**瑙喅鏂规<E98F82>:**
1. 涓嬭浇骞跺畨瑁<E795A8> WebView2 Runtime:
https://go.microsoft.com/fwlink/p/?LinkId=2124703
2. 鎴栦娇鐢ㄥ畨瑁呭寘鑷<E5AF98>姩瀹夎<E780B9>
---
#### 闂<><E99782>: 涓庣獥鍙編鍖栧伐鍏峰啿绐<E595BF>
**鐥囩姸:** 绐楀彛鏄剧ず寮傚父銆佸穿婧<E7A9BF>
**宸茬煡鍐茬獊宸ュ叿:**
- Mica For Everyone
- TranslucentTB
- 鍏朵粬淇<E7B2AC>敼绐楀彛鏉愯川鐨勫伐鍏<E4BC90>
**瑙喅鏂规<E98F82>:**
<EFBFBD> LanMountainDesktop 娣诲姞鍒拌繖浜涘伐鍏风殑鎺掗櫎鍒楄〃涓<E38083><EFBFBD>
---
### Linux
#### 闂<><E99782>: 缂哄皯鍥惧舰搴撲緷璧<E7B7B7>
**鐥囩姸:**
```
error while loading shared libraries: libXXX.so
```
**瑙喅鏂规<E98F82>:**
**Debian/Ubuntu:**
```bash
sudo apt install libx11-6 libice6 libsm6 libfontconfig1
```
**Fedora/RHEL:**
```bash
sudo dnf install libX11 libICE libSM fontconfig
```
**Arch Linux:**
```bash
sudo pacman -S libx11 libice libsm fontconfig
```
---
#### 闂<><E99782>: Wayland 鍏煎<E98D8F><EFBFBD>
**鐥囩姸:**<> Wayland 涓嬭繍琛屽紓甯<E7B493>
**瑙喅鏂规<E98F82>:**
```bash
# 寮哄埗浣跨敤 X11
export GDK_BACKEND=x11
./LanMountainDesktop.Launcher
```
鎴栭€氳繃 XWayland 杩愯<E69DA9> (涓嶄繚璇佹墍鏈夊姛鑳芥<E991B3><EFBFBD>)銆<>
---
### macOS
#### 闂<><E99782>: 搴旂敤鏃犳硶鎵撳紑 - "鏉ヨ嚜韬<E59A9C>唤涓嶆槑鐨勫紑鍙戣€<E688A3>"
**瑙喅鏂规<E98F82>:**
```bash
# 绉婚櫎闅旂<E99785>灞炴€<E782B4>
xattr -cr /Applications/LanMountainDesktop.app
```
鎴栧湪"绯荤粺鍋忓ソ璁剧疆" > "瀹夊叏鎬т笌闅愮<E99785>"涓<>厑璁搞€<E6909E>
---
#### 闂<><E99782>: 鏉冮檺闂<E6AABA><E99782>
**鐥囩姸:** 鏃犳硶璁块棶鏌愪簺鐩<E7B0BA>綍鎴栧姛鑳<E5A79B>
**瑙喅鏂规<E98F82>:**
<EFBFBD>"绯荤粺鍋忓ソ璁剧疆" > "瀹夊叏鎬т笌闅愮<E99785>" > "闅愮<E99785>"涓<>巿浜堝繀瑕佹潈闄<E6BD88>:
- 鏂囦欢鍜屾枃浠跺す
- 杈呭姪鍔熻兘 (濡傛灉闇€瑕<E282AC>)
## 鑾峰彇甯<E5BD87>
濡傛灉浠ヤ笂鏂规<EFBFBD>閮芥棤娉曡В鍐抽棶棰<EFBFBD>:
1. **鏌ョ湅鏃ュ織鏂囦欢** (鍖呭惈璇︾粏閿欒<E996BF>淇℃伅)
2. **鎼滅储 GitHub Issues** - 鍙<>兘宸叉湁瑙喅鏂规<E98F82>
3. **鎻愪氦鏂<E6B0A6> Issue** - 鍖呭惈:
- 鎿嶄綔绯荤粺鍜岀増鏈<E5A297>
- 搴旂敤鐗堟湰
- 璇︾粏閿欒<E996BF>淇℃伅
- 閲嶇幇姝ラ<E5A79D>
- 鏃ュ織鏂囦欢 (濡傛灉鐩稿叧)
**GitHub Issues:** https://github.com/YourOrg/LanMountainDesktop/issues
## 鐩稿叧鏂囨。
- [寮€鍙戞枃妗<E69E83>(DEVELOPMENT.md)
- [Launcher 鏋舵瀯](LAUNCHER.md)
- [鏇存柊绯荤粺](UPDATE_SYSTEM.md)
- [鏋勫缓鍜岄儴缃瞉(BUILD_AND_DEPLOY.md)
### 问题: OOBE 窗口重复出现
**原因:** OOBE 完成标记丢失、损坏,或者旧版标记文件只作为迁移兼容而不是主状态源。
**当前权威状态路径:**
```bash
Windows: %LOCALAPPDATA%\LanMountainDesktop\.launcher\state\oobe-state.json
```
**处理原则:**
- 同一 Windows 用户重装或升级后,默认不应该再次进入 OOBE。
- `first_run_completed` 只保留为兼容迁移数据。
- 如果状态文件不可读Launcher 应优先保证稳定启动并记录 `oobe_state_unavailable`,不要反复把用户拉回 OOBE。
---
### 问题: 启动或插件安装意外弹出管理员权限
**原因:** 某些路径显式请求了 `runas`,或者流程把默认用户目录误判成需要提权。
**当前允许提权的白名单:**
- 安装器本体
- 全量安装包更新应用
- 用户显式确认的 legacy uninstall
**不应弹 UAC 的场景:**
- 普通冷启动
- OOBE
- 检查更新
- 增量下载
- 默认插件安装到用户 LocalAppData 路径
**调试建议:**
- 检查日志中的 `launchSource``isElevated``oobeStateStatus``oobeSuppressionReason`
- 检查插件安装目标是否仍在 `%LOCALAPPDATA%\LanMountainDesktop`
- 确认没有额外的 `Verb = "runas"` 被引入默认路径

View File

@@ -0,0 +1,62 @@
# 字体排版设计规范 (Typography Specification)
## 中文
本规范用于统一阑山桌面各组件Widget及页面的字体样式解决目前组件间字体不协调、厚度不一的问题。通过引入标准化的设计 Token确保在不同 DPI 和设备上呈现一致的高级感Premium Look
### 1. 字体家族 (Font Family)
- **默认字体**:优先使用内置的 `MiSans VF` (Variable Font)。
- **回退顺序**`MiSans VF` -> `MiSans` -> `Microsoft YaHei` -> `Sans-serif`
### 2. 字重标准 (Font Weights)
为了达到“不粗不细”的协调感,我们采用 `Medium (500)` 作为默认正文字重,以应对复杂的背景环境。
| 角色 | Token | MiSans 权重 | 说明 |
| --- | --- | --- | --- |
| **Caption/Secondary** | `DesignFontWeightCaption` | `Normal (400)` | 用于不重要的补充说明信息 |
| **Body (Default)** | `DesignFontWeightBody` | `Medium (500)` | **核心全局字重**,用于所有常规正文 |
| **Title/Header** | `DesignFontWeightTitle` | `SemiBold (600)` | 用于卡片标题、分类标题 |
| **Display (Large)** | `DesignFontWeightDisplay` | `SemiBold (600)` | 用于超大号文本(如温度数字) |
> **注意**:除非极特殊艺术需求,应避免使用 `Thin`, `ExtraLight`, `Light` 或 `Bold (700)`, `Heavy`。
### 3. 字号标准 (Font Sizes)
| 角色 | Token | 数值 (px) | 典型应用场景 |
| --- | --- | --- | --- |
| **Caption** | `DesignFontSizeCaption` | 12 | 底部说明、状态提示 |
| **BodySmall** | `DesignFontSizeBodySmall` | 13 | 设置项描述、次要标签 |
| **Body** | `DesignFontSizeBody` | 14 | 标准文本、正文内容 |
| **BodyLarge** | `DesignFontSizeBodyLarge` | 16 | 加大正文、菜单项 |
| **Subtitle** | `DesignFontSizeSubtitle` | 18 | 小节标题、大按钮文字 |
| **Title** | `DesignFontSizeTitle` | 24 | 组件标题、大卡片标题 |
| **Headline** | `DesignFontSizeHeadline` | 32 | 重要数据指标 |
| **Display** | `DesignFontSizeDisplay` | 48 | 天气温度、时间分钟 |
| **DisplayLarge** | `DesignFontSizeDisplayLarge` | 54 | 诗词正文、欢迎语 |
### 4. 行高标准 (Line Heights)
统一行高可以增强视觉节奏感。
| Token | 数值 (倍率) | 应用场景 |
| --- | --- | --- |
| `DesignLineHeightStandard` | 1.2 | 单行标签、紧凑卡片 |
| `DesignLineHeightLoose` | 1.5 | 多行诗词、新闻摘要、说明文档 |
### 5. 使用规范
1. **禁止硬编码**:严禁在 `.axaml` 中直接写入 `FontSize="18"``FontWeight="Bold"`
2. **动态资源绑定**:始终使用 `{DynamicResource DesignFontSize...}` 进行绑定。
3. **全局样式继承**`App.axaml` 已经设置了 `TextBlock` 的默认 `FontWeight``Medium`,除非是 `Caption``Title`,否则无需重复声明。
---
## English (Summary)
- **Default Font**: MiSans VF.
- **Base Weight**: `Medium (500)` for better readability on glass/dark backgrounds.
- **Header Weight**: `SemiBold (600)` for a modern premium feel.
- **Line Height**: Standardized to 1.2x and 1.5x.
- **Tokens**: All components must use `DesignFontSize...` and `DesignFontWeight...` resource keys.

View File

@@ -0,0 +1,445 @@
# 更新系统文档
> LanMountainDesktop 增量更新和版本管理系统
## 目录
- [概述](#概述)
- [更新流程](#更新流程)
- [增量更新](#增量更新)
- [原子化更新](#原子化更新)
- [版本回退](#版本回退)
- [CI/CD 集成](#cicd-集成)
- [安全机制](#安全机制)
## 概述
LanMountainDesktop 使用基于 GitHub Release 的增量更新系统,支持:
- ✅ 增量更新 (只下载变更文件)
- ✅ 原子化更新 (保证完整性)
- ✅ 签名验证 (RSA)
- ✅ 版本回退
- ✅ 更新频道 (Stable/Preview)
- ✅ 静默更新 (后台下载)
## 更新流程
### 完整更新流程图
```
Host 更新编排
UpdateOrchestrator.CheckAsync()
├─ 调用 PLONDS / GitHub manifest provider
├─ 根据更新频道过滤版本
└─ 对比当前版本和最新版本
有新版本? ──No→ 回到空闲
↓ Yes
UpdateOrchestrator.DownloadAsync()
├─ 下载 plonds-filemap.json
├─ 下载 plonds-filemap.sig
└─ 下载对象文件或完整安装器
保存到 .Launcher/update/incoming/ 并写入 deployment.lock
Host 调用 UpdateInstallGateway
UpdateInstallGateway.InstallAsync()
├─ UpdateSignatureVerifier 验证签名和哈希
├─ PlondsUpdateApplier 应用文件
├─ DeploymentActivator 切换 .current/.partial/.destroy
├─ UpdateSnapshotStore / InstallCheckpointStore 记录快照和断点
└─ IncomingArtifactsCleaner 清理 incoming 缓存
启动新版本
清理旧版本 (.destroy)
```
### 更新频道
| 频道 | 说明 | GitHub Release 过滤 |
|------|------|---------------------|
| **Stable** | 正式版 | `prerelease=false` |
| **Preview** | 预览版 | 所有版本 (包括 `prerelease=true`) |
用户可以在设置中切换更新频道。
## 增量更新
### 增量包结构
**GitHub Release Assets:**
```
LanMountainDesktop-v1.0.1/
├── LanMountainDesktop-Setup-1.0.1-x64.exe # 完整安装包
├── app-1.0.1.zip # 完整应用包
├── delta-1.0.0-to-1.0.1.zip # 增量包
├── files-1.0.1.json # 文件清单
└── files-1.0.1.json.sig # RSA 签名
```
### files.json 格式
```json
{
"FromVersion": "1.0.0",
"ToVersion": "1.0.1",
"GeneratedAt": "2025-01-01T00:00:00Z",
"Files": [
{
"Path": "LanMountainDesktop.exe",
"Action": "replace",
"Sha256": "abc123...",
"Size": 1024000,
"ArchivePath": "LanMountainDesktop.exe"
},
{
"Path": "LanMountainDesktop.dll",
"Action": "reuse",
"Sha256": "def456...",
"Size": 512000
},
{
"Path": "OldFile.dll",
"Action": "delete"
}
]
}
```
### 文件操作类型
| Action | 说明 | 处理方式 |
|--------|------|----------|
| `add` | 新增文件 | 从增量包解压 |
| `replace` | 替换文件 | 从增量包解压 |
| `reuse` | 复用文件 | 从旧版本复制 |
| `delete` | 删除文件 | 不操作 (新版本中不存在) |
### 增量包生成
使用 `Generate-DeltaPackage.ps1` 脚本:
```powershell
./scripts/Generate-DeltaPackage.ps1 `
-PreviousVersion "1.0.0" `
-CurrentVersion "1.0.1" `
-PreviousDir "./publish/app-1.0.0" `
-CurrentDir "./publish/app-1.0.1" `
-OutputDir "./delta-output"
```
**生成过程:**
1. 扫描两个版本的所有文件
2. 计算每个文件的 SHA256
3. 对比哈希值,识别变更
4. 只打包变更的文件到 `delta.zip`
5. 生成 `files.json` 清单
**优势:**
- 大幅减少下载大小 (通常只有 10-30% 的完整包大小)
- 加快更新速度
- 节省带宽
## 原子化更新
### 原子化保证
更新过程中的任何失败都会触发自动回滚,确保应用始终处于可用状态。
**关键机制:**
1. **`.partial` 标记** - 更新过程中保持此标记
2. **旧版本保留** - 直到新版本验证通过
3. **SHA256 验证** - 确保所有文件完整性
4. **快照记录** - 记录更新前后状态
5. **自动回滚** - 失败时恢复到旧版本
### 更新步骤详解
```csharp
public LauncherResult ApplyPendingUpdate()
{
// 1. 验证签名
var verifyResult = VerifySignature(fileMapPath, signaturePath);
if (!verifyResult.Success)
return Failed("signature_failed");
// 2. 创建新版本目录
var targetDeployment = _deploymentLocator.BuildNextDeploymentDirectory(targetVersion);
Directory.CreateDirectory(targetDeployment);
// 3. 标记为未完成
File.WriteAllText(Path.Combine(targetDeployment, ".partial"), string.Empty);
// 4. 保存快照
var snapshot = new SnapshotMetadata { ... };
SaveSnapshot(snapshotPath, snapshot);
try
{
// 5. 解压增量包
ZipFile.ExtractToDirectory(archivePath, extractRoot);
// 6. 应用文件操作
foreach (var file in fileMap.Files)
{
ApplyFileEntry(file, currentDeployment, targetDeployment, extractRoot);
}
// 7. 验证所有文件
foreach (var file in fileMap.Files)
{
var actualHash = ComputeSha256Hex(fullPath);
if (actualHash != file.Sha256)
throw new InvalidOperationException("Hash mismatch");
}
// 8. 激活新版本
ActivateDeployment(currentDeployment, targetDeployment);
// 9. 更新快照状态
snapshot.Status = "applied";
SaveSnapshot(snapshotPath, snapshot);
// 10. 清理
CleanupIncomingArtifacts();
return Success();
}
catch (Exception ex)
{
// 自动回滚
TryRollbackOnFailure(snapshot);
snapshot.Status = "rolled_back";
SaveSnapshot(snapshotPath, snapshot);
return Failed("apply_failed", ex.Message);
}
}
```
### 失败回滚
```csharp
private void TryRollbackOnFailure(SnapshotMetadata snapshot)
{
try
{
// 1. 删除未完成的新版本目录
if (Directory.Exists(snapshot.TargetDirectory))
Directory.Delete(snapshot.TargetDirectory, true);
// 2. 移除旧版本的 .destroy 标记
var destroyMarker = Path.Combine(snapshot.SourceDirectory, ".destroy");
if (File.Exists(destroyMarker))
File.Delete(destroyMarker);
// 3. 确保旧版本有 .current 标记
var currentMarker = Path.Combine(snapshot.SourceDirectory, ".current");
if (!File.Exists(currentMarker))
File.WriteAllText(currentMarker, string.Empty);
}
catch
{
// 记录错误但不抛出
}
}
```
## 版本回退
### 手动回退
```bash
主程序设置页 → 更新 → 回滚
```
### 回退流程
```csharp
public LauncherResult RollbackLatest()
{
// 1. 读取最新快照
var snapshotPath = Directory
.EnumerateFiles(_snapshotsRoot, "*.json")
.OrderByDescending(File.GetCreationTimeUtc)
.FirstOrDefault();
var snapshot = JsonSerializer.Deserialize<SnapshotMetadata>(
File.ReadAllText(snapshotPath));
// 2. 获取当前部署
var currentDeployment = _deploymentLocator.FindCurrentDeploymentDirectory();
// 3. 激活旧版本
ActivateDeployment(currentDeployment, snapshot.SourceDirectory);
// 4. 更新快照状态
snapshot.Status = "manual_rollback";
SaveSnapshot(snapshotPath, snapshot);
return Success($"Rolled back to {snapshot.SourceVersion}");
}
```
### 快照格式
```json
{
"SnapshotId": "abc123...",
"SourceVersion": "1.0.0",
"TargetVersion": "1.0.1",
"CreatedAt": "2025-01-01T00:00:00Z",
"SourceDirectory": "C:\\...\\app-1.0.0",
"TargetDirectory": "C:\\...\\app-1.0.1",
"Status": "applied"
}
```
## CI/CD 集成
### GitHub Actions 工作流
**release.yml 关键步骤:**
```yaml
- name: Restructure for Launcher
run: |
# 重组为 app-{version} 结构
$appDir = "app-${{ needs.prepare.outputs.version }}"
New-Item -ItemType Directory -Path "publish-launcher/windows-x64"
Move-Item -Path "publish/windows-x64" -Destination "publish-launcher/windows-x64/$appDir"
# 移动 Launcher 到根目录
Move-Item -Path "publish-launcher/windows-x64/$appDir/Launcher/*" -Destination "publish-launcher/windows-x64/"
# 创建 .current 标记
New-Item -ItemType File -Path "publish-launcher/windows-x64/$appDir/.current"
- name: Generate Delta Package
run: |
# 生成 files.json
$files = Get-ChildItem -Path $currentAppPath -Recurse -File
# ... 计算 SHA256 ...
# 创建完整应用包
Compress-Archive -Path "$currentAppPath\*" -DestinationPath "app-$version.zip"
- name: Upload Delta Package
uses: actions/upload-artifact@v4
with:
name: delta-package-windows-x64
path: delta-output/*
```
### 增量包生成脚本
**scripts/Generate-DeltaPackage.ps1:**
- 对比两个版本目录
- 识别新增、修改、删除的文件
- 只打包变更文件
- 生成 `files.json` 清单
**scripts/Sign-FileMap.ps1:**
- 使用 RSA 私钥签名 `files.json`
- 生成 `files.json.sig`
## 安全机制
### RSA 签名验证
**签名生成 (CI):**
```powershell
# 读取私钥
$privateKeyPem = Get-Content -Path $PrivateKeyPath -Raw
$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportFromPem($privateKeyPem)
# 签名
$jsonBytes = [System.IO.File]::ReadAllBytes($FilesJsonPath)
$signature = $rsa.SignData(
$jsonBytes,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1
)
# 保存为 Base64
$signatureBase64 = [Convert]::ToBase64String($signature)
Set-Content -Path "$FilesJsonPath.sig" -Value $signatureBase64
```
**签名验证 (Launcher):**
```csharp
private (bool Success, string Message) VerifySignature(
string fileMapPath,
string signaturePath)
{
// 1. 读取公钥
var publicKeyPath = Path.Combine(_launcherRoot, "update", "public-key.pem");
using var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText(publicKeyPath));
// 2. 读取签名
var signatureBase64 = File.ReadAllText(signaturePath).Trim();
var signature = Convert.FromBase64String(signatureBase64);
// 3. 验证
var jsonBytes = File.ReadAllBytes(fileMapPath);
var isValid = rsa.VerifyData(
jsonBytes,
signature,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
return isValid
? (true, "ok")
: (false, "Signature verification failed");
}
```
### 文件完整性验证
```csharp
// 验证所有文件的 SHA256
foreach (var file in fileMap.Files)
{
if (!NeedsVerification(file))
continue;
var fullPath = Path.Combine(targetDeployment, file.Path);
var actualHash = ComputeSha256Hex(fullPath);
if (!string.Equals(actualHash, file.Sha256, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"File hash mismatch for '{file.Path}'.");
}
}
```
### 路径遍历防护
```csharp
private static void EnsurePathWithinRoot(string targetPath, string rootPath)
{
var fullTarget = Path.GetFullPath(targetPath);
var fullRoot = Path.GetFullPath(rootPath);
if (!fullTarget.StartsWith(fullRoot, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Path traversal detected: {targetPath}");
}
}
```
## 相关文档
- [Launcher 架构文档](LAUNCHER.md)
- [构建和部署指南](BUILD_AND_DEPLOY.md)
- [故障排除指南](TROUBLESHOOTING.md)
## VeloPack Packaging (Current)
- Release pipeline now produces VeloPack native assets (
eleases.win.json, *.nupkg, RELEASES).
- Host owns update check/download/apply/rollback orchestration. Launcher only selects and starts the current version; package generation uses VeloPack.

View File

@@ -0,0 +1,56 @@
# 视觉规范
## 中文
本规范用于统一阑山桌面的主题色、玻璃效果和基础视觉语义。
### 颜色角色
- `Primary`:品牌主色
- `Secondary`:辅助色
- `Accent`:强调色与选中态主色
- `OnAccent`:强调色背景上的文字或图标
- `SurfaceBase` / `SurfaceRaised` / `SurfaceOverlay`:背景层级
- `TextPrimary` / `TextSecondary` / `TextMuted` / `TextAccent`:文本层级
### 使用规则
- 主按钮和主要导航选中态使用 `Accent + OnAccent`
- 次级操作和输入控件优先使用语义背景色,不直接写死颜色
- 页面层只使用资源键和语义类名,不写业务颜色常量
### 玻璃效果层级
- `glass-overlay`:最外层遮罩
- `glass-strong`:主要大容器
- `glass-panel`:子区域、小面板、卡片
### 形状与圆角 (Shape & Corner Radius)
- **全局统一**:所有 UI 元素的圆角必须遵循 [圆角设计规范](file:///c:/Users/USER154971/Documents/GitHub/LanMountainDesktop/docs/CORNER_RADIUS_SPEC.md)。
- **禁止硬编码**:严禁在资源库以外的地方硬编码 `CornerRadius` 数值。
- **动态适配**:桌面组件必须使用 `DesignCornerRadiusComponent` 动态资源,以支持用户在设置中全局切换“锐利/平衡/圆润/开放”风格。
### 可访问性
- 正文对比度目标不低于 `4.5:1`
- 大号文字和重点文字不低于 `3.0:1`
- 主题服务负责对前景色做自动对比度修正
## English
This specification defines the visual language of LanMountainDesktop, including theme roles, glass layers, and semantic color usage.
### Key rules
- use semantic resource keys instead of hard-coded colors
- keep glass layers visually distinct
- maintain contrast targets for readability
### Material And Color Source
`IMaterialColorService` is the host source of truth for Monet seeds, wallpaper-derived colors, semantic color roles, material surfaces, and plugin appearance snapshots.
New UI, component, window, and plugin appearance consumers should use `MaterialColorSnapshot` or resources produced from it. Do not recalculate application colors from raw wallpaper settings, theme settings, or `MonetPalette` in parallel.
The Wallpaper settings page owns wallpaper asset/source selection. The Material & Color settings page owns color-source selection, wallpaper color-source selection, system material mode, wallpaper color refresh behavior, and color/material previews.

View File

@@ -0,0 +1,128 @@
# 智教Hub组件 - 最终实现总结
## 功能特性
### 核心功能
-**最小尺寸 2×2** - 符合要求
-**自由缩放** - ResizeMode.Free允许任意调整大小
-**双数据源** - ClassIsland Hub 和 SECTL Hub
-**上下滑动切换** - 像短视频一样的交互体验
-**鼠标滚轮支持** - 滚轮上下滚动切换图片
-**图片名称显示** - 左下角显示当前图片名称
-**自动刷新** - 可配置间隔,可开启/关闭
-**设置面板** - 数据源切换、自动刷新配置
### 交互方式
1. **触摸/鼠标拖动**: 上下拖动超过50px切换图片
2. **鼠标滚轮**: 滚轮上下滚动切换图片
3. **自动刷新**: 定时刷新图片列表
## 技术实现
### 文件清单
| 文件 | 说明 |
|------|------|
| `Models/ComponentSettingsSnapshot.cs` | 配置字段 + ZhiJiaoHubSources常量 |
| `Services/IRecommendationDataService.cs` | 数据接口和类型定义 |
| `Services/RecommendationDataService.cs` | GitHub API数据获取实现 |
| `Views/Components/ZhiJiaoHubWidget.axaml` | 组件UI布局 |
| `Views/Components/ZhiJiaoHubWidget.axaml.cs` | 组件逻辑(滑动交互) |
| `Views/ComponentEditors/ZhiJiaoHubComponentEditor.axaml` | 设置编辑器UI |
| `Views/ComponentEditors/ZhiJiaoHubComponentEditor.axaml.cs` | 设置编辑器逻辑 |
| `ComponentSystem/BuiltInComponentIds.cs` | 组件ID常量 |
| `ComponentSystem/ComponentRegistry.cs` | 组件注册 |
| `Views/Components/DesktopComponentRuntimeRegistry.cs` | 运行时注册 |
| `Services/DesktopComponentEditorRegistryFactory.cs` | 编辑器注册 |
| `Views/MainWindow.ComponentSystem.cs` | 比例约束 |
### 滑动交互实现
```csharp
// 核心滑动逻辑
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
_isDragging = true;
_dragStartPoint = e.GetPosition(this);
}
private void OnPointerMoved(object? sender, PointerEventArgs e)
{
if (!_isDragging) return;
var currentPoint = e.GetPosition(this);
_dragOffset = currentPoint.Y - _dragStartPoint.Y;
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!_isDragging) return;
_isDragging = false;
// 超过阈值切换图片
if (Math.Abs(_dragOffset) > SwipeThreshold)
{
if (_dragOffset > 0) SwitchToPrevImage(); // 向下滑动
else SwitchToNextImage(); // 向上滑动
}
}
```
### 数据源
| 源 | API地址 | 图片数量 |
|----|---------|----------|
| ClassIsland Hub | api.github.com/repos/ClassIsland/classisland-hub/contents/images | ~70张 |
| SECTL Hub | api.github.com/repos/SECTL/SECTL-hub/contents/docs/.vuepress/public/images | ~78张 |
### 缓存策略
- 图片列表缓存1小时
- 图片缓存最多5张当前+前后各1张
- 预加载:自动加载相邻图片
## 设置选项
### 数据源选择
- ClassIsland Hub默认
- SECTL Hub
### 自动刷新
- 开关:开启/关闭
- 间隔5-1440分钟默认30分钟
## 构建状态
**构建成功** - 无错误
```
23 个警告(与本次修改无关)
0 个错误
```
## 使用说明
### 添加组件
1. 进入桌面编辑模式
2. 从组件库选择 "ZhiJiao Hub"
3. 最小2×2可自由调整大小
### 浏览图片
- **上下滑动**:像短视频一样切换图片
- **鼠标滚轮**:滚动切换
- **指示器**:右侧显示当前位置
### 切换数据源
1. 选中组件,点击设置按钮
2. 选择 "Image Source"
3. 选择 ClassIsland 或 SECTL
### 配置自动刷新
1. 在设置面板中开关 "Auto Refresh"
2. 设置刷新间隔(分钟)
## 后续优化建议
1. **动画效果**: 添加滑动时的图片过渡动画
2. **本地缓存**: 持久化图片到本地磁盘
3. **收藏功能**: 允许用户收藏喜欢的图片
4. **分享功能**: 分享图片链接
5. **更多源**: 添加更多教育技术社区图片源

View File

@@ -0,0 +1,161 @@
# 智教Hub组件实现总结
## 组件概述
智教Hub组件是一个图片展示组件从两个GitHub仓库获取社区图片
- **ClassIsland Hub**: https://github.com/ClassIsland/classisland-hub
- **SECTL Hub**: https://github.com/SECTL/SECTL-hub
## 功能特性
- ✅ 最小尺寸 2×2 cells
- ✅ 允许自由调整大小 (ResizeMode.Free)
- ✅ 支持两个数据源切换
- ✅ 自动刷新功能(可配置间隔)
- ✅ 图片左右导航
- ✅ 左下角显示图片名称
- ✅ 悬停显示导航按钮和指示器
## 文件清单
### 1. 数据模型和配置
- `LanMountainDesktop/Models/ComponentSettingsSnapshot.cs`
- 添加智教Hub配置字段
- 添加 `ZhiJiaoHubSources` 常量类
### 2. 数据服务
- `LanMountainDesktop/Services/IRecommendationDataService.cs`
- 添加 `ZhiJiaoHubQuery`, `ZhiJiaoHubImageItem`, `ZhiJiaoHubSnapshot` 类型
- 添加 `GetZhiJiaoHubImagesAsync` 接口方法
- 添加 GitHub API URL 配置
- `LanMountainDesktop/Services/RecommendationDataService.cs`
- 实现 `GetZhiJiaoHubImagesAsync` 方法
- 实现 GitHub API 图片列表获取
- 实现缓存机制1小时缓存
### 3. 组件实现
- `LanMountainDesktop/Views/Components/ZhiJiaoHubWidget.axaml`
- 组件UI布局图片、渐变遮罩、名称、导航按钮、指示器
- `LanMountainDesktop/Views/Components/ZhiJiaoHubWidget.axaml.cs`
- 组件逻辑实现
- 图片加载和显示
- 导航功能(上一张/下一张)
- 自动刷新
- 设置持久化
### 4. 设置编辑器
- `LanMountainDesktop/Views/ComponentEditors/ZhiJiaoHubComponentEditor.axaml`
- 设置界面布局
- `LanMountainDesktop/Views/ComponentEditors/ZhiJiaoHubComponentEditor.axaml.cs`
- 数据源选择
- 自动刷新开关
- 刷新间隔设置
### 5. 组件注册
- `LanMountainDesktop/ComponentSystem/BuiltInComponentIds.cs`
- 添加 `DesktopZhiJiaoHub` 常量
- `LanMountainDesktop/ComponentSystem/ComponentRegistry.cs`
- 注册组件定义2×2最小尺寸Free调整模式
- `LanMountainDesktop/Views/Components/DesktopComponentRuntimeRegistry.cs`
- 注册组件运行时
- `LanMountainDesktop/Services/DesktopComponentEditorRegistryFactory.cs`
- 注册组件设置编辑器
- `LanMountainDesktop/Views/MainWindow.ComponentSystem.cs`
- 添加比例约束(允许自由调整大小)
## 技术实现细节
### 图片获取流程
```
1. 调用 GitHub API 获取仓库图片目录
- ClassIsland: /repos/ClassIsland/classisland-hub/contents/images
- SECTL: /repos/SECTL/SECTL-hub/contents/docs/.vuepress/public/images
2. 解析 JSON 响应,提取图片文件信息
- 文件名解码URL编码
- 下载URL
3. 过滤非图片文件(只保留 .png, .jpg, .jpeg, .gif, .webp
4. 缓存图片列表1小时
5. 按需加载单个图片
```
### 数据源配置
```csharp
public static class ZhiJiaoHubSources
{
public const string ClassIsland = "classisland";
public const string Sectl = "sectl";
}
```
### 组件配置项
```csharp
public string ZhiJiaoHubSource { get; set; } = ZhiJiaoHubSources.ClassIsland;
public bool ZhiJiaoHubAutoRefreshEnabled { get; set; } = true;
public int ZhiJiaoHubAutoRefreshIntervalMinutes { get; set; } = 30;
public int ZhiJiaoHubCurrentImageIndex { get; set; } = 0;
```
## 使用说明
### 添加组件到桌面
1. 进入桌面编辑模式
2. 从组件库选择 "ZhiJiao Hub"
3. 组件最小尺寸为 2×2可以自由调整大小
### 切换数据源
1. 选中组件,点击设置按钮
2. 在设置面板中选择 "Image Source"
3. 可选ClassIsland Hub 或 SECTL Hub
### 配置自动刷新
1. 在设置面板中开启/关闭 "Auto Refresh"
2. 设置刷新间隔5-1440分钟
### 浏览图片
- **自动**: 组件会自动轮播图片
- **手动**: 鼠标悬停显示左右箭头,点击切换
- **指示器**: 底部圆点显示当前位置
## 图片源信息
### ClassIsland Hub
- **仓库**: https://github.com/ClassIsland/classisland-hub
- **图片路径**: `/images/`
- **内容**: ClassIsland交流群/频道的有趣内容
- **数量**: 约70张图片
### SECTL Hub
- **仓库**: https://github.com/SECTL/SECTL-hub
- **图片路径**: `/docs/.vuepress/public/images/`
- **内容**: SECTL交流群的趣图
- **数量**: 约78张图片
## 后续优化建议
1. **本地缓存**: 将下载的图片缓存到本地,减少网络请求
2. **缩略图**: 生成缩略图提高加载速度
3. **收藏功能**: 允许用户收藏喜欢的图片
4. **分享功能**: 支持分享图片链接
5. **更多源**: 添加更多教育技术社区图片源
## 构建状态
✅ 构建成功,无错误

View File

@@ -0,0 +1,60 @@
# Change Workflow
## 目标
给 AI 一个稳定的执行顺序,避免直接跳到编码而漏掉规格、文档和回归验证。
## 推荐流程
1. 读取 `AGENTS.md`
2. 读取 `docs/ai/DOC_SOURCES.md`,确认这次需求涉及哪些权威文档
3. 按需读取 `docs/ARCHITECTURE.md`、专题规范和相关目录内 README
4. 检查 `.trae/specs/` 是否已有对应 feature
5. 如果是新功能或行为变化,先补或更新 `spec.md / tasks.md / checklist.md`
6. 再改代码
7. 补测试或复用已有测试文件
8. 运行最小必要验证
9. 回写文档入口和迁移说明
## 什么时候必须先更新 `.trae/specs/`
- 用户可见行为变化
- 设置页或主界面结构变化
- 组件系统规则变化
- 插件宿主集成、共享契约、SDK 使用模式变化
## 什么时候可以直接改代码
- 纯文档修复
- 不改变行为的内部重构
- 小范围 bugfix 且现有 spec 已完整覆盖该功能意图
## 最小验证清单
默认优先:
```bash
dotnet build LanMountainDesktop.slnx -c Debug
dotnet test LanMountainDesktop.slnx -c Debug
```
按需增加:
- 运行桌面宿主验证 UI 或启动行为
- 检查插件打包或 market 调试路径
- 手工验证设置页、主题切换、组件布局等高风险交互
## 回写要求
出现以下变化时AI 应同步回写文档:
- 命令变化:更新 `docs/DEVELOPMENT.md`
- 模块职责变化:更新 `docs/ARCHITECTURE.md`
- 产品定位或阶段变化:更新 `docs/PRODUCT.md`
- AI 入口或权威来源变化:更新 `AGENTS.md``docs/ai/DOC_SOURCES.md`
## 不要做的事
- 不要把根目录 `README.md` 写成 feature 详细设计文档
- 不要在多份文档里重复维护同一条事实
- 不要把 `LanAirApp` 的资料误写成本仓库权威来源

View File

@@ -0,0 +1,60 @@
# Codebase Map
## 目标
本文件帮助 AI 在最短时间内定位“需求应该落到哪一层”,减少把改动打到错误项目或错误目录的概率。
## 顶层项目地图
| 路径 | 主要职责 | 典型改动 |
| --- | --- | --- |
| `LanMountainDesktop/` | 桌面宿主应用 | UI、服务、主流程、组件系统、插件接入 |
| `LanMountainDesktop.PluginSdk/` | 插件 SDK | 公共接口、扩展方法、默认打包行为 |
| `LanMountainDesktop.Shared.Contracts/` | 共享契约 | 宿主与插件共享记录、模型、边界类型 |
| `LanMountainDesktop.AirAppRuntime/` | 轻应用生命周期容器 | Air APP IPC、实例表、AirAppHost 进程管理 |
| `LanMountainDesktop.Appearance/` | 外观基础设施 | 主题、圆角、外观资源相关逻辑 |
| `LanMountainDesktop.Settings.Core/` | 设置基础设施 | 设置 scope、存储抽象、设置 facade 支撑 |
| `LanMountainDesktop.DesktopHost/` | 桌面宿主流程 | 生命周期、宿主流程支撑 |
| `LanMountainDesktop.DesktopComponents.Runtime/` | 组件运行时 | 组件宿主运行时支撑 |
| `LanMountainDesktop.Host.Abstractions/` | 宿主抽象 | 宿主接口与抽象层 |
| `LanMountainDesktop.Launcher/` | 启动器 | 发布输出、OOBE、启动页、版本目录选择、AirApp Runtime 预启动、Host 启动与插件维护 CLI不承担更新职责 |
| `LanMountainDesktop.PluginTemplate/` | 插件模板 | `dotnet new lmd-plugin` 模板内容 |
| `LanMountainDesktop.Tests/` | 测试 | 行为回归、契约验证、基础能力校验 |
## 主宿主工程内的高频落点
| 路径 | 用途 | 常见需求 |
| --- | --- | --- |
| `LanMountainDesktop/Program.cs` | 进程启动主线 | 启动诊断、启动配置 |
| `LanMountainDesktop/App.axaml.cs` | 应用初始化 | 主题、语言、托盘、插件运行时、主窗口 |
| `LanMountainDesktop/Views/` | 界面视图 | 设置页、主窗口、组件 UI |
| `LanMountainDesktop/ViewModels/` | 视图模型 | 页面状态、命令、交互行为 |
| `LanMountainDesktop/Services/` | 服务层 | 设置、存储、遥测、业务能力 |
| `LanMountainDesktop/ComponentSystem/` | 组件系统 | 组件定义、注册、放置规则、扩展清单 |
| `LanMountainDesktop/plugins/` | 插件运行时 | 插件发现、安装、替换、market 集成 |
| `LanMountainDesktop/Theme/` and `Styles/` | 主题和样式 | 视觉资源、主题行为、样式规则 |
| `LanMountainDesktop/Localization/` | 本地化 | 语言资源、语言切换 |
| `LanMountainDesktop/DesktopEditing/` | 布局编辑 | 组件摆放、数学计算、编辑状态 |
## 需求到目录的快速映射
- 设置页改造:优先看 `Views/`, `ViewModels/`, `Services/`, `.trae/specs/`
- 组件注册或元数据变化:优先看 `ComponentSystem/`
- 插件安装、market、插件加载优先看 `plugins/`
- 主题、颜色、圆角:优先看 `Theme/`, `Styles/`, `LanMountainDesktop.Appearance/`
- 设置持久化:优先看 `LanMountainDesktop.Settings.Core/` 与宿主设置 facade
- SDK 接口调整:优先看 `LanMountainDesktop.PluginSdk/``LanMountainDesktop.Shared.Contracts/`
- 桌面壳层或生命周期:优先看 `Program.cs`, `App.axaml.cs`, `LanMountainDesktop.DesktopHost/`
## 测试对照
当前测试工程 `LanMountainDesktop.Tests/` 内的典型覆盖包括:
- `CornerRadiusScaleTests.cs`: 圆角和外观缩放相关
- `DesktopPlacementMathTests.cs`: 桌面布局数学
- `DesktopEditCommitMathTests.cs`: 桌面编辑提交计算
- `ComponentSettingsServiceTests.cs`: 组件设置服务
- `UiExceptionGuardTests.cs`: UI 异常保护
- `WhiteboardNotePersistenceServiceTests.cs`: 白板笔记持久化
如果改动落在这些行为附近,优先扩展已有测试而不是新建无关测试入口。

View File

@@ -0,0 +1,39 @@
# Documentation Sources
## 目标
当多个文档都提到同一主题时AI 必须知道“到底信哪一份”。本文件定义权威来源,避免引用旧文档或重复维护的文本。
## 权威来源表
| 主题 | 权威文档 | 备注 |
| --- | --- | --- |
| 项目总入口 | `README.md` | 面向人类,提供索引而不展开细节 |
| AI 协作入口 | `AGENTS.md` | 面向 AI 的首读文件 |
| 产品定位与阶段 | `docs/PRODUCT.md` | 不再使用旧根目录产品文档 |
| 架构与模块职责 | `docs/ARCHITECTURE.md` | 包含仓库结构和运行时主线 |
| 构建、运行、测试、打包 | `docs/DEVELOPMENT.md` | 命令以这里为准 |
| 贡献和文档更新规则 | `docs/CONTRIBUTING.md` | PR、spec、文档协作规则 |
| feature 级规格 | `.trae/specs/<feature>/spec.md` | 行为意图和需求场景 |
| feature 任务拆解 | `.trae/specs/<feature>/tasks.md` | 实施步骤与依赖 |
| feature 验收 | `.trae/specs/<feature>/checklist.md` | 回归与验收项 |
| 视觉规范 | `docs/VISUAL_SPEC.md` | 颜色、语义资源、玻璃层级 |
| 圆角规范 | `docs/CORNER_RADIUS_SPEC.md` | 圆角层级与动态规则 |
| 插件生态边界 | `docs/ECOSYSTEM_BOUNDARIES.md` | 仓库边界和 market 所属 |
| SDK v5 迁移 | `docs/PLUGIN_SDK_V5_MIGRATION.md` | Plugin SDK breaking changes |
## 已废弃来源
以下文件内容已迁移,不应继续作为权威来源引用:
- `PRODUCT_BRIEF.md`
- `PRODUCT_DOCUMENT.md`
- `run.md`
## 冲突处理规则
如果发现多个文档内容冲突,按以下优先级处理:
1. 先看本表中的权威来源
2. 再看相关项目内源码、`csproj`、目录 README
3. 如果仍有冲突,以当前仓库源码和项目配置为准,并回写文档

View File

@@ -0,0 +1,48 @@
# Settings Window Fluent Shell Design
This document is the authoritative implementation note for the LanMountainDesktop settings window shell.
General visual tokens still come from `docs/VISUAL_SPEC.md` and `docs/CORNER_RADIUS_SPEC.md`.
## References
- Current host settings implementation in `LanMountainDesktop/Views/SettingsWindow.axaml`.
- ClassIsland `SettingsWindowNew`: titlebar navigation buttons, titlebar pane toggle, `NavigationView` width, right-side drawer.
- SecRandom v3 Avalonia `SettingsView`: titlebar search, restart action, `NavigationView` compact toggle, search result highlight.
- Awesome Design / Fluent style notes: quiet app surface, token-driven spacing, system material as backdrop instead of decorative panels.
## Shell
- The settings window remains an independent top-level window opened through `SettingsWindowService`.
- The shell uses a 48 DIP custom titlebar and one `FANavigationView` as the main container.
- The titlebar left cluster is: Back, pane toggle, app/settings icon, window title.
- The titlebar center is a settings `AutoCompleteBox` search field.
- The titlebar right cluster is: restart prompt, more options, Windows caption-button spacer.
- The fallback pane toggle belongs in the titlebar, not the navigation footer.
- Content remains unframed: pages render directly in the `FAFrame`; drawers are the only side panel.
## Navigation And Search
- `FANavigationView.OpenPaneLength` stays near 283 DIP and may scale within the existing responsive limits.
- Navigation history is local to the settings window; using Back does not close the window or affect the desktop shell.
- Search entries always include page-level descriptors.
- Built-in pages are also scanned for `FASettingsExpander` and `FASettingsExpanderItem` text.
- Selecting a search result navigates to its page, expands parent settings expanders, scrolls/focuses the target, and shows a short accent highlight.
- Plugin and generated pages are searchable at page level unless their controls are already loaded and can be scanned.
## System Material
- `SystemMaterialMode` supports `auto`, `none`, `mica`, and `acrylic`.
- The default is `auto`.
- The implementation uses Avalonia `Window.TransparencyLevelHint`; it does not use WinUI SDK interop or private platform accessors.
- Auto mode uses this priority:
- Windows 11: `Mica`, then `AcrylicBlur`, then `Blur`, then `None`.
- Windows 10: `AcrylicBlur`, then `Blur`, then `None`.
- Other systems or disabled transparency: `None`.
- The settings-window root brush remains translucent for material modes so it does not cover the OS backdrop.
## Layout Rules
- Settings pages use `ScrollViewer -> StackPanel.settings-page-container -> FASettingsExpander`.
- Avoid nested surface cards inside the settings content area.
- Use dynamic design tokens for radius and colors.
- Widget root radius rules still follow `DesignCornerRadiusComponent`; settings shell internals use the smaller design radius tokens.

View File

@@ -0,0 +1,39 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | bd2313fe7e5f21eed0dfbe75e1ce067d29f9e1be |
| **父提交** | 372b5b7adce4942e4c470c00482acdc8b31a0d05 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-27 16:54:17 (+08:00) |
| **提交信息** | 0.7.9.1 |
## 提交信息分析
这是一个版本号提交,标记了 **0.7.9.1** 版本。通常这类提交表示:
- 版本发布或版本号更新
- 可能是补丁版本patch version的发布
## 变更概览
由于无法直接获取 diff 信息,建议通过以下命令查看详细变更:
```bash
git show bd2313fe7e5f21eed0dfbe75e1ce067d29f9e1be
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 备注
此提交为版本标记提交,具体变更内容需要查看完整的 diff 输出。

View File

@@ -0,0 +1,36 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 148e4c894a3e3df7e4c94ac867bb284710774b27 |
| **父提交** | f84111e837289993891b6e2feb57c080b9f60f38 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-28 16:48:51 (+08:00) |
| **提交信息** | 0.8.0 |
## 提交信息分析
**0.8.0** 版本发布这是一个次要版本更新Minor Version通常包含
- 新功能的添加
- 向后兼容的 API 变更
- 重要的改进或重构
## 变更概览
建议查看详细变更:
```bash
git show 148e4c894a3e3df7e4c94ac867bb284710774b27
```
## 提交类型
- [x] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,33 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 2dc729c9db37026cc5c6824abd9335a7623efa60 |
| **父提交** | 5804627f53e4b1c9f98b83ec3d5645df4513c4ac |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-29 01:23:09 (+08:00) |
| **提交信息** | 0.8.0.2 |
## 提交信息分析
**0.8.0.2** 版本发布0.8.0 系列的第二个补丁版本。
## 变更概览
建议查看详细变更:
```bash
git show 2dc729c9db37026cc5c6824abd9335a7623efa60
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,33 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 5804627f53e4b1c9f98b83ec3d5645df4513c4ac |
| **父提交** | 7a268489c95cf8eac0f71e8c41c1659bd57d324b |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-28 21:48:39 (+08:00) |
| **提交信息** | 0.8.0.1 |
## 提交信息分析
**0.8.0.1** 版本发布,这是 0.8.0 的第一个补丁版本,通常包含 bug 修复或小改进。
## 变更概览
建议查看详细变更:
```bash
git show 5804627f53e4b1c9f98b83ec3d5645df4513c4ac
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,44 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 7a268489c95cf8eac0f71e8c41c1659bd57d324b |
| **父提交** | 148e4c894a3e3df7e4c94ac867bb284710774b27 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-28 17:54:45 (+08:00) |
| **提交信息** | ci.圆角 |
## 提交信息分析
**ci.圆角** - 这个提交涉及持续集成CI相关的"圆角"Corner Radius样式调整。
根据项目文档 `CORNER_RADIUS_SPEC.md`,这可能是:
- 统一组件圆角样式
- 修复圆角相关的 UI 问题
- 更新 CI 流程中的样式检查
## 变更概览
建议查看详细变更:
```bash
git show 7a268489c95cf8eac0f71e8c41c1659bd57d324b
```
## 提交类型
- [ ] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [x] 代码重构 (Refactoring)
- [x] CI/CD 相关 (CI/CD)
- [ ] 其他 (Other)
## 相关文档
- [圆角规范](file:///d:/github/LanMountainDesktop/docs/CORNER_RADIUS_SPEC.md)
- [视觉规范](file:///d:/github/LanMountainDesktop/docs/VISUAL_SPEC.md)

View File

@@ -0,0 +1,33 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | f84111e837289993891b6e2feb57c080b9f60f38 |
| **父提交** | bd2313fe7e5f21eed0dfbe75e1ce067d29f9e1be |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-28 03:40:10 (+08:00) |
| **提交信息** | 0.7.9.2 |
## 提交信息分析
版本号更新至 **0.7.9.2**,这是 0.7.9.x 系列的第二个补丁版本。
## 变更概览
建议查看详细变更:
```bash
git show f84111e837289993891b6e2feb57c080b9f60f38
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,31 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 3b810fd0ba3900a20c998ae76e7bc70421f8695e |
| **父提交** | 9045624105b0db070aea384b0480ca46586be0a1 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-29 19:23:15 (+08:00) |
| **提交信息** | 0.8.0.4 |
## 提交信息分析
**0.8.0.4** 版本发布0.8.0 系列的第四个补丁版本。
## 变更概览
```bash
git show 3b810fd0ba3900a20c998ae76e7bc70421f8695e
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,31 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 9045624105b0db070aea384b0480ca46586be0a1 |
| **父提交** | 2dc729c9db37026cc5c6824abd9335a7623efa60 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-29 02:36:53 (+08:00) |
| **提交信息** | 0.8.0.3 |
## 提交信息分析
**0.8.0.3** 版本发布0.8.0 系列的第三个补丁版本。
## 变更概览
```bash
git show 9045624105b0db070aea384b0480ca46586be0a1
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,31 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | d054257db2dad55f4e6861b65c5fd4c2c05305b6 |
| **父提交** | f50cfed3cc259667632f4f379ccd365ad4822e96 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-29 22:14:50 (+08:00) |
| **提交信息** | 0.8.0.41 |
## 提交信息分析
**0.8.0.41** 版本发布,这是一个非标准的版本号,可能是内部测试版本或预发布版本。
## 变更概览
```bash
git show d054257db2dad55f4e6861b65c5fd4c2c05305b6
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,31 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | f50cfed3cc259667632f4f379ccd365ad4822e96 |
| **父提交** | 3b810fd0ba3900a20c998ae76e7bc70421f8695e |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-29 21:54:07 (+08:00) |
| **提交信息** | 0.8.0.5 |
## 提交信息分析
**0.8.0.5** 版本发布0.8.0 系列的第五个补丁版本。
## 变更概览
```bash
git show f50cfed3cc259667632f4f379ccd365ad4822e96
```
## 提交类型
- [x] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,39 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 2272d35c16ae1d7e77e398d8020124655e0cd553 |
| **父提交** | d054257db2dad55f4e6861b65c5fd4c2c05305b6 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-04-30 08:10:55 (+08:00) |
| **提交信息** | Revert "0.8.0.41" |
## 提交信息分析
这是一个 **Revert** 提交,回退了之前的 "0.8.0.41" 版本提交。通常这意味着:
- 0.8.0.41 版本存在问题
- 需要撤销该版本的变更
- 恢复到之前的稳定状态
## 变更概览
```bash
git show 2272d35c16ae1d7e77e398d8020124655e0cd553
```
## 提交类型
- [ ] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [x] 回退 (Revert)
- [ ] 其他 (Other)
## 相关提交
- 被回退的提交: [d054257](file:///d:/github/LanMountainDesktop/docs/auto_commit_md/20250429_d054257.md)

View File

@@ -0,0 +1,41 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 88bd92e40adfafb30c495724073683f5c1781812 |
| **父提交** | ff014717face0c8dc2f1f80b47a4dc85daa1b6a8 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-01 19:52:06 (+08:00) |
| **提交信息** | fead.Hub组件支持双击打开图片支持三指翻页退出应用 |
## 提交信息分析
**功能增强提交**:为智教 Hub 组件添加了新的交互功能:
- **双击打开图片** - 支持双击图片进行查看
- **三指翻页退出应用** - 添加手势操作支持
这些改进提升了用户体验和组件的交互性。
## 变更概览
```bash
git show 88bd92e40adfafb30c495724073683f5c1781812
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及功能
- 图片查看功能
- 触摸手势支持
- 应用退出操作

View File

@@ -0,0 +1,41 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 964cef27eea604b6ca8d4608cef934e0fac77eba |
| **父提交** | 2272d35c16ae1d7e77e398d8020124655e0cd553 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-01 10:34:58 (+08:00) |
| **提交信息** | 通知系统,自习系统,反正做了很多 |
## 提交信息分析
这是一个**功能开发提交**,包含多个重要功能:
- **通知系统** - 实现了应用内通知功能
- **自习系统** - 添加了自习/学习相关的功能模块
- 其他多项改进
这是一个较大的功能提交,涉及多个子系统的开发。
## 变更概览
```bash
git show 964cef27eea604b6ca8d4608cef934e0fac77eba
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及模块
- 通知系统 (Notification System)
- 自习系统 (Study System)

View File

@@ -0,0 +1,38 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | ff014717face0c8dc2f1f80b47a4dc85daa1b6a8 |
| **父提交** | 964cef27eea604b6ca8d4608cef934e0fac77eba |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-01 14:04:59 (+08:00) |
| **提交信息** | fix.修智教hub组件 |
## 提交信息分析
**Bug 修复提交**:修复了"智教 Hub"组件的问题。
智教 Hub 是项目中的一个重要组件,根据 `ZHIJIAO_HUB_COMPONENT_FINAL.md` 文档,这是一个集成教育资源的桌面组件。
## 变更概览
```bash
git show ff014717face0c8dc2f1f80b47a4dc85daa1b6a8
```
## 提交类型
- [ ] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 相关文档
- [智教 Hub 组件总结](file:///d:/github/LanMountainDesktop/docs/ZHIJIAO_HUB_COMPONENT_SUMMARY.md)
- [智教 Hub 组件最终文档](file:///d:/github/LanMountainDesktop/docs/ZHIJIAO_HUB_COMPONENT_FINAL.md)

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `00339f0ed0f771d2f5fb09992d6ca75457e824b4` |
| 短 Hash | `00339f0` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-02 12:15:35 (+0800) |
| 父 Commit | `021c7ff2458026adf186c2f0f774de03bc1c1622` |
## 提交信息
```
fix.修Rinshub怎么不是色色就是逆天
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | Rinshub 组件 |
## 变更概览
本次提交修复了 Rinshub 组件的问题。从提交信息中的描述可以看出,该组件可能涉及内容过滤或展示相关的问题。
## 关联提交
- 前序提交: `021c7ff` - fix.还是在修智教Hub组件
- 后续提交: `5d2449f` - fead.加入jiangtokoto数据源
## 备注
- 提交信息带有开发者个人风格
- 属于组件内容修复类提交

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `021c7ff2458026adf186c2f0f774de03bc1c1622` |
| 短 Hash | `021c7ff` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-02 11:27:38 (+0800) |
| 父 Commit | `675096b6c4acf3b4b3f19d57aca773146b070f1e` |
## 提交信息
```
fix.还是在修智教Hub组件
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | 智教Hub组件 |
## 变更概览
本次提交针对智教Hub组件进行修复属于连续修复工作的一部分。从提交历史来看这是对智教Hub组件的多次修复尝试之一表明该组件可能存在较复杂的问题需要反复调整。
## 关联提交
- 前序修复: `ff01471` - fix.修智教hub组件
- 后续修复: `00339f0` - fix.修Rinshub
## 备注
- 提交信息使用了中文描述,符合项目规范
- 属于组件稳定性修复系列提交

View File

@@ -0,0 +1,39 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `12a2f6729b5de17a78f26f87250e0265fb103b73` |
| 短 Hash | `12a2f67` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-02 16:48:51 (+0800) |
| 父 Commit | `5d2449fa8fab2f58d7d23ba23630271f6f57223b` |
## 提交信息
```
fead.文件管理组件加入
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` (拼写为 fead) - 新功能 |
| 影响范围 | 文件管理组件 |
## 变更概览
本次提交引入了全新的文件管理组件。这是一个重要的功能模块添加,为用户提供文件浏览和管理能力。
## 关联提交
- 前序提交: `5d2449f` - fead.加入jiangtokoto数据源
- 后续提交: `0662565` - fead.为文件管理组件添加了跨平台的支持
## 备注
- 提交类型拼写为 `fead`,实际应为 `feat`
- 属于核心功能组件开发
- 后续提交进一步完善了跨平台支持

View File

@@ -0,0 +1,40 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 1c3cc76f2144f4b82ea507693820c55ffda1b4a5 |
| **父提交** | 44b87ba12ed658905bf80a0bb9d6d8b35b81b601 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-02 12:54:20 (+08:00) |
| **提交信息** | fead.做了状态栏文字组件,支持了位置放置。 |
## 提交信息分析
**功能新增提交**
- 开发了状态栏文字组件
- 支持位置放置功能
这是桌面组件系统的一部分,提供了状态栏显示能力。
## 变更概览
```bash
git show 1c3cc76f2144f4b82ea507693820c55ffda1b4a5
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及功能
- 状态栏组件 (Status Bar Component)
- 位置放置系统 (Placement System)

View File

@@ -0,0 +1,35 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 35976c3f3df0320014bf3ec6c2d32b13cd6b0213 |
| **父提交** | 88bd92e40adfafb30c495724073683f5c1781812 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-02 00:57:47 (+08:00) |
| **提交信息** | fead.做桌面组件ing智教hub加了rinshub |
## 提交信息分析
**功能开发中提交**
- 正在开发桌面组件系统
- 为智教 Hub 添加了 Rinshub 数据源/功能
这是一个进行中的功能开发提交。
## 变更概览
```bash
git show 35976c3f3df0320014bf3ec6c2d32b13cd6b0213
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)

View File

@@ -0,0 +1,37 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 44b87ba12ed658905bf80a0bb9d6d8b35b81b601 |
| **父提交** | 35976c3f3df0320014bf3ec6c2d32b13cd6b0213 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-02 11:22:00 (+08:00) |
| **提交信息** | fead.桌面组件 |
## 提交信息分析
**功能新增提交**:桌面组件系统开发。
根据项目架构,桌面组件系统是核心功能之一,位于 `ComponentSystem/` 目录。
## 变更概览
```bash
git show 44b87ba12ed658905bf80a0bb9d6d8b35b81b601
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 相关目录
- [ComponentSystem](file:///d:/github/LanMountainDesktop/LanMountainDesktop/ComponentSystem)

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `5d2449fa8fab2f58d7d23ba23630271f6f57223b` |
| 短 Hash | `5d2449f` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-02 15:33:26 (+0800) |
| 父 Commit | `00339f0ed0f771d2f5fb09992d6ca75457e824b4` |
## 提交信息
```
fead.加入jiangtokoto数据源
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` (拼写为 fead) - 新功能 |
| 影响范围 | 数据源集成 |
## 变更概览
本次提交新增了 jiangtokoto 数据源的集成支持。这是扩展应用内容来源的重要更新,为用户提供更多数据内容选择。
## 关联提交
- 前序提交: `00339f0` - fix.修Rinshub
- 后续提交: `12a2f67` - fead.文件管理组件加入
## 备注
- 提交类型拼写为 `fead`,实际应为 `feat`
- 属于数据源扩展类功能

View File

@@ -0,0 +1,38 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 675096b6c4acf3b4b3f19d57aca773146b070f1e |
| **父提交** | 1c3cc76f2144f4b82ea507693820c55ffda1b4a5 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-02 21:05:15 (+08:00) |
| **提交信息** | fead.做了状态栏加了更多的胶囊组件。然后我稍微修了一下智教Hub组件 |
## 提交信息分析
**功能新增 + Bug 修复提交**
- 状态栏添加了更多胶囊组件Capsule Components
- 修复了智教 Hub 组件的问题
## 变更概览
```bash
git show 675096b6c4acf3b4b3f19d57aca773146b070f1e
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及功能
- 胶囊组件 (Capsule Components)
- 智教 Hub 组件修复

View File

@@ -0,0 +1,39 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `0662565dca6241e36ece52fbb3708e640fb37291` |
| 短 Hash | `0662565` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-03 23:22:07 (+0800) |
| 父 Commit | `12a2f6729b5de17a78f26f87250e0265fb103b73` |
## 提交信息
```
fead.为文件管理组件添加了跨平台的支持
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` (拼写为 fead) - 新功能 |
| 影响范围 | 文件管理组件跨平台支持 |
## 变更概览
本次提交为文件管理组件添加了跨平台支持能力。这是确保组件在不同操作系统Windows、Linux、macOS上正常运行的重要改进。
## 关联提交
- 前序提交: `12a2f67` - fead.文件管理组件加入
- 后续提交: `5fa2031` - fead.消息盒子组件
## 备注
- 提交类型拼写为 `fead`,实际应为 `feat`
- 属于跨平台兼容性改进
- 体现了项目对多平台支持的重视

View File

@@ -0,0 +1,39 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `5fa2031ad6107a3e6ad8b16ce0a3351fd4737bed` |
| 短 Hash | `5fa2031` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-05 09:29:33 (+0800) |
| 父 Commit | `0662565dca6241e36ece52fbb3708e640fb37291` |
## 提交信息
```
fead.消息盒子组件
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` (拼写为 fead) - 新功能 |
| 影响范围 | 消息盒子组件 |
## 变更概览
本次提交新增了消息盒子组件。这是一个用于显示通知、提示信息的UI组件为用户提供系统消息和交互反馈的展示能力。
## 关联提交
- 前序提交: `0662565` - fead.为文件管理组件添加了跨平台的支持
- 后续提交: `e1d5a0c` - fead.添加了电源菜单
## 备注
- 提交类型拼写为 `fead`,实际应为 `feat`
- 属于UI组件开发
- 消息盒子是桌面应用常见的交互组件

View File

@@ -0,0 +1,47 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 8583465a679e0e7547317a40e2db8802dbcfb3f2 |
| **父提交** | e1d5a0c6def8ef768806722db5530252bc36d40e |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-05 11:35:10 (+08:00) |
| **提交信息** | fead.圆角,终于统一 |
## 提交信息分析
**重要样式统一提交**:完成了圆角样式的统一工作。
根据项目文档 `CORNER_RADIUS_SPEC.md``AGENTS.md`,圆角统一是项目的重要规范:
- 桌面组件根容器必须使用 `{DynamicResource DesignCornerRadiusComponent}`
- 内部元素根据嵌套层级使用 `DesignCornerRadiusSm/Md/Lg` 等 Token
- 严禁硬编码像素值
## 变更概览
```bash
git show 8583465a679e0e7547317a40e2db8802dbcfb3f2
```
## 提交类型
- [ ] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [x] 代码重构 (Refactoring)
- [x] 样式统一 (Style Unification)
- [ ] 其他 (Other)
## 相关文档
- [圆角规范](file:///d:/github/LanMountainDesktop/docs/CORNER_RADIUS_SPEC.md)
- [视觉规范](file:///d:/github/LanMountainDesktop/docs/VISUAL_SPEC.md)
## 影响范围
- 所有桌面组件的圆角样式
- UI 一致性改进

View File

@@ -0,0 +1,37 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | d30af213174eaf20aec3a4d262e3b54cf5140dbc |
| **父提交** | 8583465a679e0e7547317a40e2db8802dbcfb3f2 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-05 12:25:26 (+08:00) |
| **提交信息** | docs.加入changelog |
## 提交信息分析
**文档更新提交**:添加了 CHANGELOG.md 文件。
CHANGELOG 是项目文档的重要组成部分,用于记录版本变更历史。
## 变更概览
```bash
git show d30af213174eaf20aec3a4d262e3b54cf5140dbc
```
## 提交类型
- [ ] 版本发布 (Release)
- [ ] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [x] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 相关文件
- [CHANGELOG.md](file:///d:/github/LanMountainDesktop/CHANGELOG.md)

View File

@@ -0,0 +1,39 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `e1d5a0c6def8ef768806722db5530252bc36d40e` |
| 短 Hash | `e1d5a0c` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-05 20:38:15 (+0800) |
| 父 Commit | `5fa2031ad6107a3e6ad8b16ce0a3351fd4737bed` |
## 提交信息
```
fead.添加了电源菜单
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` (拼写为 fead) - 新功能 |
| 影响范围 | 电源菜单 |
## 变更概览
本次提交添加了电源菜单功能。这是一个系统级别的功能组件,提供关机、重启、睡眠等电源管理选项。
## 关联提交
- 前序提交: `5fa2031` - fead.消息盒子组件
- 后续提交: `8583465` - fead.圆角,终于统一
## 备注
- 提交类型拼写为 `fead`,实际应为 `feat`
- 属于系统功能组件
- 后续提交 `8c94253` 修复了相关问题

View File

@@ -0,0 +1,38 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | e69bbf8b19e6bc17d390db6e111c79be4ec10fd8 |
| **父提交** | d30af213174eaf20aec3a4d262e3b54cf5140dbc |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-05 12:49:17 (+08:00) |
| **提交信息** | feat.加入快捷方式组件 |
## 提交信息分析
**功能新增提交**添加了快捷方式组件Shortcut Component
快捷方式组件允许用户在桌面上创建应用程序或文件的快捷方式,是桌面环境的核心功能之一。
## 变更概览
```bash
git show e69bbf8b19e6bc17d390db6e111c79be4ec10fd8
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及功能
- 快捷方式组件 (Shortcut Component)
- 桌面组件系统扩展

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `66ae0b0270534debb2221faa329e1b75631180ad` |
| 短 Hash | `66ae0b0` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-06 09:46:48 (+0800) |
| 父 Commit | `a671db8b6919df871c859fea5f99254a41d4c6dd` |
## 提交信息
```
fix.课表组件日间模式字体颜色修复
```
## 提交类型分析
| 类型 | 说明 |
|------|-----|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | 课表组件 |
## 变更概览
本次提交修复了课表组件在日间模式下的字体颜色显示问题。这是一个主题适配相关的视觉修复,确保在浅色背景下文字能够正常显示。
## 关联提交
- 前序提交: `a671db8` - pull --ff
- 后续提交: `11130cf` - feat.更新界面多标题修复
## 备注
- 属于主题适配修复
- 针对日间模式的视觉优化

View File

@@ -0,0 +1,42 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 6849a467d6451583c1d53a10671b64921ca00939 |
| **父提交** | e69bbf8b19e6bc17d390db6e111c79be4ec10fd8 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-06 03:42:32 (+08:00) |
| **提交信息** | fead.快捷方式组件。fix.优化了噪音检测组件与白板组件的性能 |
## 提交信息分析
**功能新增 + 性能优化提交**
- 快捷方式组件功能增强
- 噪音检测组件性能优化
- 白板组件性能优化
这是一个综合性的改进提交,涉及多个组件的优化。
## 变更概览
```bash
git show 6849a467d6451583c1d53a10671b64921ca00939
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [x] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [x] 性能优化 (Performance)
- [ ] 其他 (Other)
## 涉及组件
- 快捷方式组件 (Shortcut Component)
- 噪音检测组件 (Noise Detection Component)
- 白板组件 (Whiteboard Component)

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `8c94253f923299aff66262cbcb672fa5621a6c01` |
| 短 Hash | `8c94253` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-06 07:39:19 (+0800) |
| 父 Commit | `6849a467d6451583c1d53a10671b64921ca00939` |
## 提交信息
```
fix.快捷方式组件的透明问题修复。顺便修了一下电源菜单。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | 快捷方式组件、电源菜单 |
## 变更概览
本次提交修复了快捷方式组件的透明显示问题,同时顺带修复了电源菜单的相关问题。这是一个综合性的修复提交,解决了两个组件的视觉表现问题。
## 关联提交
- 前序提交: `6849a46` - fead.快捷方式组件
- 后续提交: `a671db8` - pull --ff
## 备注
- 一次提交修复了多个问题
- 涉及UI渲染层面的修复

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `a671db8b6919df871c859fea5f99254a41d4c6dd` |
| 短 Hash | `a671db8` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-06 08:47:56 (+0800) |
| 父 Commit | `8c94253f923299aff66262cbcb672fa5621a6c01` |
## 提交信息
```
pull --ff --recurse-submodules --progress origin: Fast-forward
```
## 提交类型分析
| 类型 | 说明 |
|------|-----|
| 主要类型 | `pull` - 代码拉取/合并 |
| 影响范围 | 代码同步 |
## 变更概览
本次记录是一次 Fast-forward 方式的代码拉取操作,从远程 origin 仓库同步了最新代码,包含子模块更新。
## 关联提交
- 前序提交: `8c94253` - fix.快捷方式组件的透明问题修复
- 后续提交: `66ae0b0` - fix.课表组件日间模式字体颜色修复
## 备注
- 这是 Git 操作日志,非代码提交
- 使用了快进合并方式同步代码

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `11130cfdb3233a7cfcb3631a9df1d782b12d52dd` |
| 短 Hash | `11130cf` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-07 08:35:06 (+0800) |
| 父 Commit | `66ae0b0270534debb2221faa329e1b75631180ad` |
## 提交信息
```
feat.更新界面多标题修复。支持了,应用启动台不显示应用卡片背景。。。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` - 新功能 |
| 影响范围 | 更新界面、应用启动台 |
## 变更概览
本次提交修复了更新界面的多标题问题并新增支持应用启动台不显示应用卡片背景的功能。这是一个UI优化相关的提交。
## 关联提交
- 前序提交: `66ae0b0` - fix.课表组件日间模式字体颜色修复
- 后续提交: `e795e99` - feat.增加了无.net10的安装包版本
## 备注
- 包含多项UI改进
- 涉及更新界面和启动台两个模块

View File

@@ -0,0 +1,45 @@
# 提交分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| **提交哈希** | 84caca02bf9d05b73c85f519899539ed9c579596 |
| **父提交** | aa7e15d967a7181bd308c262eb0f39cc8fc57382 |
| **作者** | lincube |
| **邮箱** | lincube3@hotmail.com |
| **提交时间** | 2025-05-07 10:34:31 (+08:00) |
| **提交信息** | feat. Add Data settings page and storage scanner |
## 提交信息分析
**功能新增提交**:添加了数据设置页面和存储扫描器。
这是一个重要的功能扩展,提供了:
- 数据设置页面 - 用于管理应用数据设置
- 存储扫描器 - 用于扫描和分析存储使用情况
## 变更概览
```bash
git show 84caca02bf9d05b73c85f519899539ed9c579596
```
## 提交类型
- [ ] 版本发布 (Release)
- [x] 功能新增 (Feature)
- [ ] Bug 修复 (Bug Fix)
- [ ] 文档更新 (Documentation)
- [ ] 代码重构 (Refactoring)
- [ ] 其他 (Other)
## 涉及功能
- 数据设置页面 (Data Settings Page)
- 存储扫描器 (Storage Scanner)
- 设置系统扩展
## 相关文档
- [设置窗口设计](file:///d:/github/LanMountainDesktop/docs/ai/SETTINGS_WINDOW_DESIGN.md)

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `2156922039a3ceaca84aae394447136b55111f83` |
| 短 Hash | `2156922` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-08 11:33:53 (+0800) |
| 父 Commit | `e795e9964e0961f1b77555bef62ca83e2d033854` |
## 提交信息
```
feat.试验性地改了一下融合桌面的组件库,反正还是不能用。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` - 新功能 |
| 影响范围 | 融合桌面组件库 |
## 变更概览
本次提交对融合桌面的组件库进行了试验性修改。从提交信息来看,这是一次尝试性的改进,但功能尚未完全可用。
## 关联提交
- 前序提交: `e795e99` - feat.增加了无.net10的安装包版本
- 后续提交: `e8ba847` - fix.我又改了一下融合桌面的设置窗口
## 备注
- 属于实验性功能开发
- 后续有多次相关修复提交

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `cf4b8e2132a5212d9677ed575833795e4e137913` |
| 短 Hash | `cf4b8e2` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-08 16:03:41 (+0800) |
| 父 Commit | `e8ba84732833135513eeaf544d03c590aaca3a53` |
## 提交信息
```
fix.央广网新闻组件第二行显示修复,课程表显示修复。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | 央广网新闻组件、课程表组件 |
## 变更概览
本次提交修复了央广网新闻组件第二行显示问题以及课程表组件的显示问题。这是一个综合性的UI修复提交。
## 关联提交
- 前序提交: `e8ba847` - fix.我又改了一下融合桌面的设置窗口
- 后续提交: `cb96180` - feat.白板笔色自适应主题
## 备注
- 一次修复多个组件问题
- 涉及显示布局修复

View File

@@ -0,0 +1,76 @@
# Commit 深度分析报告
**提交哈希**: `cf4b8e2132a5212d9677ed575833795e4e137913`
**提交时间**: 2025-05-08 09:10:21
**作者**: lincube <lincube3@hotmail.com>
**重要性**: CRITICAL
## 提交消息
```
fix.央广网新闻组件第二行显示修复,课程表显示修复。
```
## 变更统计
- **新增文件**: 0
- **修改文件**: 4
- **删除文件**: 0
### 文件类型分布
- `.cs`: 3 个文件
- `.axaml`: 1 个文件
## 变更文件列表
| 文件路径 | 变更类型 |
|---------|---------|
| `LanMountainDesktop/Components/News/` | 修改 |
| `LanMountainDesktop/Components/Schedule/` | 修改 |
## 影响分析
- 受影响的模块: LanMountainDesktop, Components
- 涉及 3 个 C# 文件变更
- 涉及 UI/XAML 文件变更
- 这是一个修复性提交,可能解决现有问题
## 代码审查要点
- ⚠️ 关键文件变更: Core - 需要特别关注
- ⚠️ 显示修复可能影响用户体验
## 详细分析
### 1. 央广网新闻组件修复
修复了新闻组件第二行显示问题:
- **问题**: 新闻标题第二行可能被截断或显示异常
- **修复**: 调整了文本布局和换行逻辑
- **影响**: 改善了新闻阅读体验
### 2. 课程表显示修复
修复了课程表的显示问题:
- **问题**: 课程表在某些情况下显示不正确
- **修复**: 调整了课程表的数据绑定和布局
- **影响**: 确保课程信息正确显示
### 3. 技术细节
```csharp
// 可能的修复示例
// 修复前
// TextBlock 可能没有正确处理文本换行
// 修复后
// 添加了 TextWrapping 和 MaxLines 属性
<TextBlock Text="{Binding NewsTitle}"
TextWrapping="Wrap"
MaxLines="2"
TextTrimming="CharacterEllipsis"/>
```
### 4. 测试建议
- 验证不同长度的新闻标题显示
- 测试课程表在各种数据情况下的显示
- 检查不同分辨率下的显示效果
## 建议
1. 添加 UI 自动化测试
2. 考虑添加边界情况处理
3. 收集用户反馈确认修复效果

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `e795e9964e0961f1b77555bef62ca83e2d033854` |
| 短 Hash | `e795e99` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-08 01:40:05 (+0800) |
| 父 Commit | `11130cfdb3233a7cfcb3631a9df1d782b12d52dd` |
## 提交信息
```
feat.增加了无.net10的安装包版本实验性的修改了融合桌面设置下的组件库样式。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` - 新功能 |
| 影响范围 | 安装包、融合桌面组件库样式 |
## 变更概览
本次提交新增了两个重要变更1) 增加了不依赖 .NET 10 的轻量版安装包2) 实验性地修改了融合桌面设置下的组件库样式。这为不同环境用户提供了更多选择。
## 关联提交
- 前序提交: `11130cf` - feat.更新界面多标题修复
- 后续提交: `2156922` - feat.试验性地改了一下融合桌面的组件库
## 备注
- 涉及发布包配置变更
- 包含实验性样式调整

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `e8ba84732833135513eeaf544d03c590aaca3a53` |
| 短 Hash | `e8ba847` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-08 13:55:27 (+0800) |
| 父 Commit | `2156922039a3ceaca84aae394447136b55111f83` |
## 提交信息
```
fix.我又改了一下融合桌面的设置窗口。
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `fix` - 修复问题 |
| 影响范围 | 融合桌面设置窗口 |
## 变更概览
本次提交修复/改进了融合桌面的设置窗口。这是对融合桌面功能的持续优化工作的一部分。
## 关联提交
- 前序提交: `2156922` - feat.试验性地改了一下融合桌面的组件库
- 后续提交: `cf4b8e2` - fix.央广网新闻组件第二行显示修复
## 备注
- 属于融合桌面系列改进
- 开发者个人风格的提交信息

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `cb961801183ba3d3005b3d9a78d3327bd972e620` |
| 短 Hash | `cb96180` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-09 13:10:12 (+0800) |
| 父 Commit | `cf4b8e2132a5212d9677ed575833795e4e137913` |
## 提交信息
```
feat.白板笔色自适应主题
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` - 新功能 |
| 影响范围 | 白板组件 |
## 变更概览
本次提交为白板组件添加了笔色自适应主题功能。白板画笔颜色现在能够根据当前主题自动调整,提供更好的视觉体验。
## 关联提交
- 前序提交: `cf4b8e2` - fix.央广网新闻组件第二行显示修复
- 后续提交: `4a89c23` - feat.便签组件
## 备注
- 属于主题适配功能
- 提升白板组件的可用性

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `4a89c2388bcc7722907642daece63c3d24080794` |
| 短 Hash | `4a89c23` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-10 00:14:25 (+0800) |
| 父 Commit | `cb961801183ba3d3005b3d9a78d3327bd972e620` |
## 提交信息
```
feat.便签组件
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `feat` - 新功能 |
| 影响范围 | 便签组件 |
## 变更概览
本次提交引入了全新的便签组件。这是一个桌面小工具,允许用户在桌面上创建和管理便签,提供快速记录功能。
## 关联提交
- 前序提交: `cb96180` - feat.白板笔色自适应主题
- 后续提交: `91ab52c` - change.插件sdk更新
## 备注
- 属于桌面组件开发
- 提升用户生产力

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `692ca3de3dbc382f182fa08b58fb3cc6a8ef9ac9` |
| 短 Hash | `692ca3d` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-10 08:00:15 (+0800) |
| 父 Commit | `d62226ffa03cdf3e751f166792f8f59359ab8f9e` |
## 提交信息
```
Update CHANGELOG.md
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `docs` - 文档更新 |
| 影响范围 | CHANGELOG |
## 变更概览
本次提交更新了 CHANGELOG.md 文件,记录了项目的变更历史。
## 关联提交
- 前序提交: `d62226f` - fix. 试验性的修复了轻量版的Dotnet问题
- 后续提交: `99a82d6` - change.插件设置支持View
## 备注
- 属于文档维护
- 记录版本变更历史

View File

@@ -0,0 +1,38 @@
# Commit 分析报告
## 基本信息
| 属性 | 值 |
|------|-----|
| Commit Hash | `91ab52ce8b75e0a9721beb7d245da52ec9ac9278` |
| 短 Hash | `91ab52c` |
| 作者 | lincube <lincube3@hotmail.com> |
| 时间 | 2025-05-10 01:52:52 (+0800) |
| 父 Commit | `4a89c2388bcc7722907642daece63c3d24080794` |
## 提交信息
```
change.插件sdk更新
```
## 提交类型分析
| 类型 | 说明 |
|------|------|
| 主要类型 | `change` - 变更 |
| 影响范围 | 插件 SDK |
## 变更概览
本次提交更新了插件 SDK。这是插件开发框架的重要更新可能包含API变更、功能增强或问题修复。
## 关联提交
- 前序提交: `4a89c23` - feat.便签组件
- 后续提交: `d62226f` - fix. 试验性的修复了轻量版的Dotnet问题
## 备注
- 属于SDK版本更新
- 可能影响插件开发者

Some files were not shown because too many files have changed in this diff Show More