From 1b22e9df4a139481e0133aa8c50565e4e07ee083 Mon Sep 17 00:00:00 2001 From: lincube Date: Mon, 13 Apr 2026 19:54:37 +0800 Subject: [PATCH] =?UTF-8?q?feat.=E6=96=B0=E5=A2=9E=E4=BA=86=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=BC=80=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +- docs/Plugins develop/00-索引与导航.md | 118 ++++++ .../01-快速开始/01-开发环境准备.md | 220 ++++++++++ .../01-快速开始/02-三分钟创建第一个插件.md | 236 +++++++++++ .../01-快速开始/03-插件项目结构详解.md | 350 +++++++++++++++ .../01-快速开始/04-调试运行指南.md | 380 +++++++++++++++++ .../02-核心概念与原理/01-插件生命周期.md | 398 ++++++++++++++++++ .../02-核心概念与原理/02-桌面组件系统.md | 393 +++++++++++++++++ .../02-核心概念与原理/03-设置系统集成.md | 353 ++++++++++++++++ .../02-核心概念与原理/04-外观与主题系统.md | 308 ++++++++++++++ .../02-核心概念与原理/05-插件间通信.md | 375 +++++++++++++++++ .../03-API实践指南/01-PluginBase详解.md | 307 ++++++++++++++ .../03-API实践指南/02-组件注册与配置.md | 182 ++++++++ .../03-API实践指南/03-设置API详解.md | 147 +++++++ .../03-API实践指南/04-外观API详解.md | 106 +++++ .../03-API实践指南/05-本地化支持.md | 73 ++++ .../06-CI-CD与自动化/01-GitHub Actions入门.md | 215 ++++++++++ .../06-CI-CD与自动化/02-配置自动构建.md | 130 ++++++ .../06-CI-CD与自动化/03-自动打包与发布.md | 156 +++++++ .../07-发布与运营/01-插件打包规范.md | 92 ++++ .../07-发布与运营/02-版本管理策略.md | 75 ++++ .../07-发布与运营/03-发布到插件市场.md | 71 ++++ .../07-发布与运营/04-更新与维护.md | 82 ++++ 23 files changed, 4770 insertions(+), 1 deletion(-) create mode 100644 docs/Plugins develop/00-索引与导航.md create mode 100644 docs/Plugins develop/01-快速开始/01-开发环境准备.md create mode 100644 docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md create mode 100644 docs/Plugins develop/01-快速开始/03-插件项目结构详解.md create mode 100644 docs/Plugins develop/01-快速开始/04-调试运行指南.md create mode 100644 docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md create mode 100644 docs/Plugins develop/02-核心概念与原理/02-桌面组件系统.md create mode 100644 docs/Plugins develop/02-核心概念与原理/03-设置系统集成.md create mode 100644 docs/Plugins develop/02-核心概念与原理/04-外观与主题系统.md create mode 100644 docs/Plugins develop/02-核心概念与原理/05-插件间通信.md create mode 100644 docs/Plugins develop/03-API实践指南/01-PluginBase详解.md create mode 100644 docs/Plugins develop/03-API实践指南/02-组件注册与配置.md create mode 100644 docs/Plugins develop/03-API实践指南/03-设置API详解.md create mode 100644 docs/Plugins develop/03-API实践指南/04-外观API详解.md create mode 100644 docs/Plugins develop/03-API实践指南/05-本地化支持.md create mode 100644 docs/Plugins develop/06-CI-CD与自动化/01-GitHub Actions入门.md create mode 100644 docs/Plugins develop/06-CI-CD与自动化/02-配置自动构建.md create mode 100644 docs/Plugins develop/06-CI-CD与自动化/03-自动打包与发布.md create mode 100644 docs/Plugins develop/07-发布与运营/01-插件打包规范.md create mode 100644 docs/Plugins develop/07-发布与运营/02-版本管理策略.md create mode 100644 docs/Plugins develop/07-发布与运营/03-发布到插件市场.md create mode 100644 docs/Plugins develop/07-发布与运营/04-更新与维护.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b2fa1f2..f108ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,9 @@ ### 修复 (Fixed) -- 无 +- 🐛 **快捷方式组件透明问题**: 修复了快捷方式组件无法正常透明的问题 + - 问题原因: 组件背景透明属性设置异常或渲染层级问题 + - 修复方案: 修正透明属性配置,确保快捷方式组件背景透明效果正常显示 ### 移除 (Removed) diff --git a/docs/Plugins develop/00-索引与导航.md b/docs/Plugins develop/00-索引与导航.md new file mode 100644 index 0000000..5d53126 --- /dev/null +++ b/docs/Plugins develop/00-索引与导航.md @@ -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月* diff --git a/docs/Plugins develop/01-快速开始/01-开发环境准备.md b/docs/Plugins develop/01-快速开始/01-开发环境准备.md new file mode 100644 index 0000000..849e14d --- /dev/null +++ b/docs/Plugins develop/01-快速开始/01-开发环境准备.md @@ -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 进行开发: + +### 选项 1:Visual Studio 2022(推荐 Windows 用户) + +**优点:** 功能最全,调试体验最佳 + +1. 下载 [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) +2. 安装时选择以下工作负载: + - ✅ **.NET 桌面开发** + - ✅ **Avalonia UI 开发**(可选,如需 Avalonia 设计器) + +### 选项 2:JetBrains Rider(跨平台推荐) + +**优点:** 跨平台,智能提示强大,Avalonia 支持好 + +1. 下载 [Rider](https://www.jetbrains.com/rider/) +2. 安装后打开,会自动检测 .NET SDK + +### 选项 3:Visual 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 | 应用正常启动 | + +--- + +## ⚠️ 常见问题 + +### 问题 1:dotnet 命令找不到 + +**现象:** 运行 `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 + ``` + +### 问题 3:SDK 版本不匹配 + +**现象:** 构建时提示 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月* diff --git a/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md new file mode 100644 index 0000000..3107848 --- /dev/null +++ b/docs/Plugins develop/01-快速开始/02-三分钟创建第一个插件.md @@ -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": "4.0.1", + "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月* diff --git a/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md new file mode 100644 index 0000000..c355107 --- /dev/null +++ b/docs/Plugins develop/01-快速开始/03-插件项目结构详解.md @@ -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": "4.0.1", + "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 版本 | `4.0.1` | +| `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 版本兼容 +- 当前最新版本:`4.0.1` +- 不兼容时宿主将拒绝加载插件 + +--- + +## 🔧 .csproj - 项目文件 + +定义了项目的构建配置和依赖项。 + +### 完整示例 + +```xml + + + + net10.0 + enable + enable + latest + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + +``` + +### 关键配置项 + +| 配置项 | 说明 | 推荐值 | +|-------|------|--------| +| `TargetFramework` | 目标框架 | `net10.0` | +| `LangVersion` | C# 语言版本 | `latest` | +| `Nullable` | 可空引用类型 | `enable` | + +### SDK 引用 + +```xml + +``` + +⚠️ **版本必须匹配:** +- SDK 版本必须与 `plugin.json` 中的 `apiVersion` 兼容 +- 建议使用最新稳定版 + +### 资源文件配置 + +确保 `plugin.json` 和本地化文件被正确复制到输出目录: + +```xml + + + PreserveNewest + + + PreserveNewest + + +``` + +--- + +## 🚪 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(...); + + // 示例:注册设置页面 + // 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>(); +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月* diff --git a/docs/Plugins develop/01-快速开始/04-调试运行指南.md b/docs/Plugins develop/01-快速开始/04-调试运行指南.md new file mode 100644 index 0000000..61e601a --- /dev/null +++ b/docs/Plugins develop/01-快速开始/04-调试运行指南.md @@ -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 + + + + + ``` + +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 _logger; + + public MyService(ILogger 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 + + true + + ``` + +### 问题 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月* diff --git a/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md new file mode 100644 index 0000000..36148da --- /dev/null +++ b/docs/Plugins develop/02-核心概念与原理/01-插件生命周期.md @@ -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(); + + // 2. 注册桌面组件 + services.AddPluginDesktopComponent( + 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 v4 暂无显式的卸载回调方法 +- 资源释放依赖 .NET 垃圾回收 +- 建议: + - 使用 `IDisposable` 模式管理资源 + - 在组件卸载事件中清理资源 + +--- + +## ⏱️ 启动时序图 + +``` +阑山桌面 插件系统 你的插件 + │ │ │ + │── 启动 ───────►│ │ + │ │ │ + │ │── 发现插件 ───►│ + │ │ │ (读取 plugin.json) + │ │◄───────────────│ + │ │ │ + │ │── 加载 DLL ───►│ + │ │ │ (AssemblyLoadContext) + │ │◄───────────────│ + │ │ │ + │ │── 创建实例 ───►│ + │ │ │ (调用构造函数) + │ │◄───────────────│ + │ │ │ + │ │── Initialize ─►│ + │ │ │ (注册组件/服务) + │ │◄───────────────│ + │ │ │ + │◄───────────────│ │ + │ │ │ + │── UI就绪 ─────►│ │ + │ │ │ + │ │── 用户添加组件 ─►│ + │ │ │ (创建组件实例) +``` + +--- + +## 💡 最佳实践 + +### 1. Initialize 方法保持轻量 + +```csharp +// ✅ 好的做法:快速注册 +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + services.AddSingleton(); + services.AddPluginDesktopComponent(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(); + + // 在 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) { } +} +``` + +--- + +## 🐛 常见问题 + +### 问题 1:Initialize 中访问 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月* diff --git a/docs/Plugins develop/02-核心概念与原理/02-桌面组件系统.md b/docs/Plugins develop/02-核心概念与原理/02-桌面组件系统.md new file mode 100644 index 0000000..295d61d --- /dev/null +++ b/docs/Plugins develop/02-核心概念与原理/02-桌面组件系统.md @@ -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( + 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("city", defaultValue: "北京"); + var autoRefresh = _settings.GetValue("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月* diff --git a/docs/Plugins develop/02-核心概念与原理/03-设置系统集成.md b/docs/Plugins develop/02-核心概念与原理/03-设置系统集成.md new file mode 100644 index 0000000..c02d35f --- /dev/null +++ b/docs/Plugins develop/02-核心概念与原理/03-设置系统集成.md @@ -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( + sectionId: "myplugin-advanced", + displayName: "高级设置", + iconKey: "Settings"); +} +``` + +### 混合模式 + +可以同时使用声明式设置和自定义视图: + +```csharp +services.AddPluginSettingsSection( + sectionId: "myplugin-settings", + displayName: "插件设置", + configure: section => section + .SetCustomView() // 设置自定义视图 + .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("api_key", ""); + var autoRefresh = _settings.GetValue("auto_update", true); + var interval = _settings.GetValue("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("city", "北京"); + var unit = settings.GetValue("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月* diff --git a/docs/Plugins develop/02-核心概念与原理/04-外观与主题系统.md b/docs/Plugins develop/02-核心概念与原理/04-外观与主题系统.md new file mode 100644 index 0000000..3f99211 --- /dev/null +++ b/docs/Plugins develop/02-核心概念与原理/04-外观与主题系统.md @@ -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 + + + + + + + +``` + +### 常用 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月* diff --git a/docs/Plugins develop/02-核心概念与原理/05-插件间通信.md b/docs/Plugins develop/02-核心概念与原理/05-插件间通信.md new file mode 100644 index 0000000..45f3c02 --- /dev/null +++ b/docs/Plugins develop/02-核心概念与原理/05-插件间通信.md @@ -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(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(OnWeatherUpdated); + } + + public void Dispose() + { + // 取消订阅,避免内存泄漏 + _messageBus.Unsubscribe(_subscriptionId); + } +} +``` + +--- + +## 🔌 服务导出 + +插件可以将服务导出,供其他插件使用。 + +### 导出服务 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + // 注册服务 + services.AddSingleton(); + + // 导出服务供其他插件使用 + services.AddPluginServiceExport( + serviceKey: "MyPlugin.WeatherService", + description: "提供天气查询服务"); +} + +// 定义服务接口 +public interface IWeatherService +{ + Task GetCurrentWeatherAsync(string city); + Task> GetForecastAsync(string city, int days); +} + +// 实现服务 +public class WeatherService : IWeatherService +{ + public async Task GetCurrentWeatherAsync(string city) + { + // 实现天气查询 + } + + public async Task> GetForecastAsync(string city, int days) + { + // 实现天气预报 + } +} +``` + +### 使用其他插件的服务 + +```csharp +public class MyWidget : Border +{ + public MyWidget(PluginDesktopComponentContext context) + { + // 获取其他插件导出的服务 + var weatherService = context.ServiceProvider + .GetExportedService("MyPlugin.WeatherService"); + + if (weatherService != null) + { + // 使用服务 + LoadWeatherAsync(weatherService); + } + } + + private async void LoadWeatherAsync(IWeatherService weatherService) + { + var weather = await weatherService.GetCurrentWeatherAsync("北京"); + UpdateUI(weather); + } +} +``` + +### 服务导出选项 + +```csharp +services.AddPluginServiceExport( + 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 GetDataAsync(); +} + +internal class InternalService : IPublicApi +{ + // 内部实现细节不暴露 + private readonly SecretKey _key; + + public async Task GetDataAsync() + { + // 实现 + } +} +``` + +### 消息验证 + +```csharp +private void OnMessageReceived(MyMessage message) +{ + // 验证消息来源 + if (message.SenderId != "TrustedPlugin") + { + return; // 忽略不信任来源的消息 + } + + // 处理消息 +} +``` + +--- + +## 💡 最佳实践 + +### 1. 使用接口定义服务契约 + +```csharp +// ✅ 好的做法 - 定义接口 +public interface IWeatherService { } + +// ❌ 避免 - 直接导出实现类 +services.AddPluginServiceExport(...); +``` + +### 2. 处理服务不可用情况 + +```csharp +// ✅ 优雅处理服务缺失 +var service = context.ServiceProvider + .GetExportedService("key"); + +if (service == null) +{ + // 显示提示或降级处理 + ShowServiceUnavailableMessage(); + return; +} +``` + +### 3. 及时取消消息订阅 + +```csharp +// ✅ 在 Dispose 中取消订阅 +public void Dispose() +{ + _messageBus.Unsubscribe(_subscriptionId); +} +``` + +### 4. 版本兼容性 + +```csharp +// 在服务导出中包含版本信息 +services.AddPluginServiceExport( + 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月* diff --git a/docs/Plugins develop/03-API实践指南/01-PluginBase详解.md b/docs/Plugins develop/03-API实践指南/01-PluginBase详解.md new file mode 100644 index 0000000..9ecdda0 --- /dev/null +++ b/docs/Plugins develop/03-API实践指南/01-PluginBase详解.md @@ -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(); + + 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(); +} +``` + +### 注册作用域服务 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + // 作用域 - 每个作用域一个实例 + services.AddScoped(); +} +``` + +### 注册瞬态服务 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + // 瞬态 - 每次请求都创建新实例 + services.AddTransient(); +} +``` + +### 带配置的服务注册 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + services.AddSingleton(provider => + { + var httpClient = provider.GetRequiredService(); + var logger = provider.GetRequiredService>(); + 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(); + services.AddSingleton(); + + // 3. 注册桌面组件 + services.AddPluginDesktopComponent( + 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(); + +// 再注册依赖它们的服务 +services.AddSingleton(); // 依赖 IDataService + +// 最后注册组件 +services.AddPluginDesktopComponent(options); +``` + +### 3. 记录初始化过程 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + Logger?.LogInformation("开始初始化..."); + + Logger?.LogDebug("注册服务..."); + services.AddSingleton(); + + Logger?.LogDebug("注册组件..."); + services.AddPluginDesktopComponent(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月* diff --git a/docs/Plugins develop/03-API实践指南/02-组件注册与配置.md b/docs/Plugins develop/03-API实践指南/02-组件注册与配置.md new file mode 100644 index 0000000..3a9c7b1 --- /dev/null +++ b/docs/Plugins develop/03-API实践指南/02-组件注册与配置.md @@ -0,0 +1,182 @@ +# 02-组件注册与配置 + +`AddPluginDesktopComponent` 是注册桌面组件的核心 API。本文详细讲解其用法和配置选项。 + +--- + +## 🎯 API 概览 + +```csharp +public static IServiceCollection AddPluginDesktopComponent( + this IServiceCollection services, + PluginDesktopComponentOptions options) + where TComponent : class, IControl +``` + +--- + +## 📋 基本用法 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + services.AddPluginDesktopComponent( + 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( + new PluginDesktopComponentOptions + { + ComponentId = "MyPlugin.Widget", + DisplayName = "我的组件", + IconKey = "Home", + Category = "工具", + MinWidthCells = 4, + MinHeightCells = 3, + CornerRadiusPreset = PluginCornerRadiusPreset.Component + }); +``` + +--- + +## 📐 调整大小模式 + +```csharp +services.AddPluginDesktopComponent( + new PluginDesktopComponentOptions + { + // ... + ResizeMode = PluginDesktopComponentResizeMode.Free + }); +``` + +| 模式 | 说明 | +|-----|------| +| `Free` | 自由调整大小 | +| `Fixed` | 固定大小 | +| `AspectRatio` | 保持宽高比 | + +--- + +## 🧩 完整示例 + +```csharp +public override void Initialize(HostBuilderContext context, IServiceCollection services) +{ + // 天气组件 + services.AddPluginDesktopComponent( + new PluginDesktopComponentOptions + { + ComponentId = "WeatherPlugin.Widget", + DisplayName = "天气", + IconKey = "Weather", + Category = "信息", + MinWidthCells = 4, + MinHeightCells = 3, + CornerRadiusPreset = PluginCornerRadiusPreset.Component, + ResizeMode = PluginDesktopComponentResizeMode.Free + }); + + // 时钟组件 + services.AddPluginDesktopComponent( + 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月* diff --git a/docs/Plugins develop/03-API实践指南/03-设置API详解.md b/docs/Plugins develop/03-API实践指南/03-设置API详解.md new file mode 100644 index 0000000..f4a3ae5 --- /dev/null +++ b/docs/Plugins develop/03-API实践指南/03-设置API详解.md @@ -0,0 +1,147 @@ +# 03-设置API详解 + +设置 API 允许插件添加配置页面和持久化用户设置。 + +--- + +## 🎯 API 概览 + +### 声明式设置 + +```csharp +services.AddPluginSettingsSection( + string sectionId, + string displayName, + Action configure, + string iconKey); +``` + +### 自定义设置页 + +```csharp +services.AddPluginSettingsSection( + 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("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月* diff --git a/docs/Plugins develop/03-API实践指南/04-外观API详解.md b/docs/Plugins develop/03-API实践指南/04-外观API详解.md new file mode 100644 index 0000000..0108d99 --- /dev/null +++ b/docs/Plugins develop/03-API实践指南/04-外观API详解.md @@ -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月* diff --git a/docs/Plugins develop/03-API实践指南/05-本地化支持.md b/docs/Plugins develop/03-API实践指南/05-本地化支持.md new file mode 100644 index 0000000..6669760 --- /dev/null +++ b/docs/Plugins develop/03-API实践指南/05-本地化支持.md @@ -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 _localizer; + + public MyService(IStringLocalizer 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月* diff --git a/docs/Plugins develop/06-CI-CD与自动化/01-GitHub Actions入门.md b/docs/Plugins develop/06-CI-CD与自动化/01-GitHub Actions入门.md new file mode 100644 index 0000000..2e42945 --- /dev/null +++ b/docs/Plugins develop/06-CI-CD与自动化/01-GitHub Actions入门.md @@ -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月* diff --git a/docs/Plugins develop/06-CI-CD与自动化/02-配置自动构建.md b/docs/Plugins develop/06-CI-CD与自动化/02-配置自动构建.md new file mode 100644 index 0000000..78038e2 --- /dev/null +++ b/docs/Plugins develop/06-CI-CD与自动化/02-配置自动构建.md @@ -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月* diff --git a/docs/Plugins develop/06-CI-CD与自动化/03-自动打包与发布.md b/docs/Plugins develop/06-CI-CD与自动化/03-自动打包与发布.md new file mode 100644 index 0000000..6f41876 --- /dev/null +++ b/docs/Plugins develop/06-CI-CD与自动化/03-自动打包与发布.md @@ -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月* diff --git a/docs/Plugins develop/07-发布与运营/01-插件打包规范.md b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md new file mode 100644 index 0000000..5b99533 --- /dev/null +++ b/docs/Plugins develop/07-发布与运营/01-插件打包规范.md @@ -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": "4.0.1", + "entranceAssembly": "MyPlugin.dll", + "sharedContracts": [] +} +``` + +### 字段验证规则 + +| 字段 | 规则 | +|-----|------| +| `id` | 小写字母、数字、点号,反向域名格式 | +| `version` | 语义化版本(x.y.z) | +| `apiVersion` | 必须与 SDK 版本兼容 | +| `entranceAssembly` | 必须与 DLL 文件名一致 | + +--- + +## 🔨 构建配置 + +### .csproj 关键配置 + +```xml + + + net10.0 + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + +``` + +--- + +## ✅ 打包检查清单 + +- [ ] `plugin.json` 格式正确 +- [ ] `id` 全局唯一 +- [ ] `apiVersion` 与 SDK 版本匹配 +- [ ] DLL 文件名与 `entranceAssembly` 一致 +- [ ] 所有依赖项已包含 +- [ ] 本地化文件完整 + +--- + +*最后更新:2026年4月* diff --git a/docs/Plugins develop/07-发布与运营/02-版本管理策略.md b/docs/Plugins develop/07-发布与运营/02-版本管理策略.md new file mode 100644 index 0000000..2833b99 --- /dev/null +++ b/docs/Plugins develop/07-发布与运营/02-版本管理策略.md @@ -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月* diff --git a/docs/Plugins develop/07-发布与运营/03-发布到插件市场.md b/docs/Plugins develop/07-发布与运营/03-发布到插件市场.md new file mode 100644 index 0000000..e4b0af5 --- /dev/null +++ b/docs/Plugins develop/07-发布与运营/03-发布到插件市场.md @@ -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月* diff --git a/docs/Plugins develop/07-发布与运营/04-更新与维护.md b/docs/Plugins develop/07-发布与运营/04-更新与维护.md new file mode 100644 index 0000000..69f3314 --- /dev/null +++ b/docs/Plugins develop/07-发布与运营/04-更新与维护.md @@ -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月*