mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 01:44:26 +08:00
790 lines
20 KiB
Markdown
790 lines
20 KiB
Markdown
# 组件系统详解
|
||
|
||
本文档详细介绍阑山桌面的桌面组件(Widget)系统,包括组件架构、生命周期、渲染机制和最佳实践。
|
||
|
||
## 什么是桌面组件?
|
||
|
||
**桌面组件(Widget)** 是显示在桌面上的可视化模块,提供信息展示和快捷操作功能。
|
||
|
||
### 组件特点
|
||
|
||
- 🎨 **固定在桌面** - 显示在桌面图层,不会被普通窗口遮挡
|
||
- 🔄 **实时更新** - 定时刷新数据,保持信息最新
|
||
- ⚙️ **可配置** - 用户可以自定义组件行为和外观
|
||
- 🖱️ **可交互** - 支持点击、拖拽等用户操作
|
||
- 📐 **可布局** - 用户可以自由调整位置和大小
|
||
|
||
### 典型组件示例
|
||
|
||
| 组件类型 | 功能 | 更新频率 |
|
||
|---------|------|---------|
|
||
| **时钟组件** | 显示当前时间和日期 | 1秒 |
|
||
| **天气组件** | 显示天气信息 | 5-15分钟 |
|
||
| **日历组件** | 显示日程和待办 | 1小时 |
|
||
| **系统监控** | CPU、内存使用率 | 2秒 |
|
||
| **倒计时** | 重要日期倒计时 | 1秒 |
|
||
|
||
## 组件架构
|
||
|
||
### 组件三层结构
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ Component (组件模型) │
|
||
│ ┌──────────────────────────────────┐ │
|
||
│ │ 业务逻辑 │ │
|
||
│ │ - 数据获取 │ │
|
||
│ │ - 状态管理 │ │
|
||
│ │ - 设置持久化 │ │
|
||
│ └──────────────────────────────────┘ │
|
||
└────────────────┬───────────────────────┘
|
||
│ 数据绑定
|
||
┌────────────────▼───────────────────────┐
|
||
│ ViewModel (视图模型) │
|
||
│ ┌──────────────────────────────────┐ │
|
||
│ │ 展示逻辑 │ │
|
||
│ │ - 属性通知 │ │
|
||
│ │ - 命令处理 │ │
|
||
│ │ - 数据格式化 │ │
|
||
│ └──────────────────────────────────┘ │
|
||
└────────────────┬───────────────────────┘
|
||
│ UI 绑定
|
||
┌────────────────▼───────────────────────┐
|
||
│ View (视图) │
|
||
│ ┌──────────────────────────────────┐ │
|
||
│ │ UI 渲染 │ │
|
||
│ │ - Avalonia AXAML │ │
|
||
│ │ - 样式和主题 │ │
|
||
│ │ - 用户交互 │ │
|
||
│ └──────────────────────────────────┘ │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
### 组件基类层次
|
||
|
||
```
|
||
object
|
||
↓
|
||
ObservableObject (MVVM Toolkit)
|
||
↓
|
||
ComponentBase (Plugin SDK)
|
||
↓
|
||
YourComponent (你的组件)
|
||
```
|
||
|
||
## 创建组件
|
||
|
||
### 步骤 1: 定义组件类
|
||
|
||
```csharp
|
||
using LanMountainDesktop.PluginSdk.Components;
|
||
using LanMountainDesktop.Shared.Contracts.Components;
|
||
using System.ComponentModel;
|
||
|
||
namespace MyPlugin.Components;
|
||
|
||
/// <summary>
|
||
/// 天气组件 - 显示当前天气信息
|
||
/// </summary>
|
||
[Component(
|
||
Id = "com.example.myplugin.weather",
|
||
Name = "天气",
|
||
Description = "显示当前天气和温度",
|
||
Category = "信息",
|
||
Icon = "avares://MyPlugin/Assets/weather-icon.png",
|
||
DefaultWidth = 200,
|
||
DefaultHeight = 150
|
||
)]
|
||
public class WeatherComponent : ComponentBase
|
||
{
|
||
// 组件唯一标识
|
||
public override string Id => "com.example.myplugin.weather";
|
||
|
||
// 组件显示名称
|
||
public override string Name => "天气";
|
||
|
||
// === 数据属性 ===
|
||
|
||
private string _location = "北京";
|
||
private double _temperature = 0;
|
||
private string _condition = "晴";
|
||
private string _icon = "☀️";
|
||
|
||
/// <summary>
|
||
/// 位置
|
||
/// </summary>
|
||
public string Location
|
||
{
|
||
get => _location;
|
||
set => SetProperty(ref _location, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 温度(摄氏度)
|
||
/// </summary>
|
||
public double Temperature
|
||
{
|
||
get => _temperature;
|
||
set => SetProperty(ref _temperature, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 天气状况
|
||
/// </summary>
|
||
public string Condition
|
||
{
|
||
get => _condition;
|
||
set => SetProperty(ref _condition, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 天气图标
|
||
/// </summary>
|
||
public string Icon
|
||
{
|
||
get => _icon;
|
||
set => SetProperty(ref _icon, value);
|
||
}
|
||
|
||
// === 配置属性 ===
|
||
|
||
private bool _useFahrenheit = false;
|
||
|
||
/// <summary>
|
||
/// 是否使用华氏度
|
||
/// </summary>
|
||
public bool UseFahrenheit
|
||
{
|
||
get => _useFahrenheit;
|
||
set
|
||
{
|
||
if (SetProperty(ref _useFahrenheit, value))
|
||
{
|
||
// 保存到设置
|
||
Settings.SetValue("UseFahrenheit", value);
|
||
// 触发更新
|
||
OnPropertyChanged(nameof(DisplayTemperature));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示温度(根据单位)
|
||
/// </summary>
|
||
public string DisplayTemperature
|
||
{
|
||
get
|
||
{
|
||
if (UseFahrenheit)
|
||
{
|
||
var fahrenheit = Temperature * 9 / 5 + 32;
|
||
return $"{fahrenheit:F1}°F";
|
||
}
|
||
return $"{Temperature:F1}°C";
|
||
}
|
||
}
|
||
|
||
// === 生命周期方法 ===
|
||
|
||
/// <summary>
|
||
/// 组件初始化
|
||
/// </summary>
|
||
public override async Task InitializeAsync()
|
||
{
|
||
// 从设置加载配置
|
||
Location = Settings.GetValue("Location", "北京");
|
||
UseFahrenheit = Settings.GetValue("UseFahrenheit", false);
|
||
|
||
// 首次加载数据
|
||
await FetchWeatherDataAsync();
|
||
|
||
Logger.LogInformation($"WeatherComponent initialized for {Location}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组件定时更新
|
||
/// </summary>
|
||
public override async Task UpdateAsync()
|
||
{
|
||
// 每 10 分钟更新一次天气数据
|
||
var lastUpdate = Settings.GetValue<DateTime>("LastUpdate", DateTime.MinValue);
|
||
if (DateTime.Now - lastUpdate > TimeSpan.FromMinutes(10))
|
||
{
|
||
await FetchWeatherDataAsync();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组件销毁
|
||
/// </summary>
|
||
public override void Dispose()
|
||
{
|
||
// 清理资源
|
||
base.Dispose();
|
||
}
|
||
|
||
// === 业务逻辑 ===
|
||
|
||
private HttpClient? _httpClient;
|
||
|
||
private async Task FetchWeatherDataAsync()
|
||
{
|
||
try
|
||
{
|
||
_httpClient ??= new HttpClient();
|
||
|
||
// 调用天气 API
|
||
var url = $"https://api.weather.com/data?city={Location}";
|
||
var response = await _httpClient.GetStringAsync(url);
|
||
|
||
// 解析数据
|
||
var weatherData = ParseWeatherData(response);
|
||
|
||
// 更新属性
|
||
Temperature = weatherData.Temperature;
|
||
Condition = weatherData.Condition;
|
||
Icon = GetWeatherIcon(weatherData.Condition);
|
||
|
||
// 记录更新时间
|
||
Settings.SetValue("LastUpdate", DateTime.Now);
|
||
|
||
Logger.LogInformation($"Weather data updated for {Location}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.LogError(ex, "Failed to fetch weather data");
|
||
Condition = "加载失败";
|
||
}
|
||
}
|
||
|
||
private WeatherData ParseWeatherData(string json)
|
||
{
|
||
// 解析 JSON 数据
|
||
// 实际项目中使用 System.Text.Json 或 Newtonsoft.Json
|
||
return new WeatherData
|
||
{
|
||
Temperature = 25.5,
|
||
Condition = "晴"
|
||
};
|
||
}
|
||
|
||
private string GetWeatherIcon(string condition)
|
||
{
|
||
return condition switch
|
||
{
|
||
"晴" => "☀️",
|
||
"多云" => "⛅",
|
||
"阴" => "☁️",
|
||
"雨" => "🌧️",
|
||
"雪" => "❄️",
|
||
_ => "🌤️"
|
||
};
|
||
}
|
||
|
||
private class WeatherData
|
||
{
|
||
public double Temperature { get; set; }
|
||
public string Condition { get; set; } = "";
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤 2: 创建视图
|
||
|
||
创建 `Views/WeatherComponentView.axaml`:
|
||
|
||
```xml
|
||
<UserControl xmlns="https://github.com/avaloniaui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:vm="using:MyPlugin.ViewModels"
|
||
x:Class="MyPlugin.Views.WeatherComponentView"
|
||
x:DataType="vm:WeatherComponentViewModel">
|
||
|
||
<!-- 组件容器 -->
|
||
<Border Background="{DynamicResource CardBackgroundBrush}"
|
||
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
|
||
Padding="16"
|
||
BorderBrush="{DynamicResource CardBorderBrush}"
|
||
BorderThickness="1"
|
||
BoxShadow="0 2 8 0 #20000000">
|
||
|
||
<Grid RowDefinitions="Auto,*,Auto">
|
||
|
||
<!-- 标题栏 -->
|
||
<StackPanel Grid.Row="0"
|
||
Orientation="Horizontal"
|
||
Spacing="8"
|
||
Margin="0,0,0,12">
|
||
<TextBlock Text="📍" FontSize="16" />
|
||
<TextBlock Text="{Binding Component.Location}"
|
||
FontSize="14"
|
||
FontWeight="SemiBold"
|
||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||
</StackPanel>
|
||
|
||
<!-- 主要内容 -->
|
||
<StackPanel Grid.Row="1"
|
||
HorizontalAlignment="Center"
|
||
VerticalAlignment="Center"
|
||
Spacing="8">
|
||
|
||
<!-- 天气图标 -->
|
||
<TextBlock Text="{Binding Component.Icon}"
|
||
FontSize="48"
|
||
HorizontalAlignment="Center" />
|
||
|
||
<!-- 温度 -->
|
||
<TextBlock Text="{Binding Component.DisplayTemperature}"
|
||
FontSize="32"
|
||
FontWeight="Bold"
|
||
HorizontalAlignment="Center"
|
||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||
|
||
<!-- 天气状况 -->
|
||
<TextBlock Text="{Binding Component.Condition}"
|
||
FontSize="16"
|
||
HorizontalAlignment="Center"
|
||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||
|
||
</StackPanel>
|
||
|
||
<!-- 底部操作 -->
|
||
<StackPanel Grid.Row="2"
|
||
Orientation="Horizontal"
|
||
HorizontalAlignment="Right"
|
||
Spacing="8"
|
||
Margin="0,12,0,0">
|
||
|
||
<!-- 刷新按钮 -->
|
||
<Button Command="{Binding RefreshCommand}"
|
||
Padding="8,4"
|
||
ToolTip.Tip="刷新">
|
||
<TextBlock Text="🔄" FontSize="14" />
|
||
</Button>
|
||
|
||
<!-- 设置按钮 -->
|
||
<Button Command="{Binding SettingsCommand}"
|
||
Padding="8,4"
|
||
ToolTip.Tip="设置">
|
||
<TextBlock Text="⚙️" FontSize="14" />
|
||
</Button>
|
||
|
||
</StackPanel>
|
||
|
||
</Grid>
|
||
|
||
</Border>
|
||
|
||
</UserControl>
|
||
```
|
||
|
||
代码后台 `WeatherComponentView.axaml.cs`:
|
||
|
||
```csharp
|
||
using Avalonia.Controls;
|
||
|
||
namespace MyPlugin.Views;
|
||
|
||
public partial class WeatherComponentView : UserControl
|
||
{
|
||
public WeatherComponentView()
|
||
{
|
||
InitializeComponent();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤 3: 创建视图模型
|
||
|
||
创建 `ViewModels/WeatherComponentViewModel.cs`:
|
||
|
||
```csharp
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using MyPlugin.Components;
|
||
|
||
namespace MyPlugin.ViewModels;
|
||
|
||
/// <summary>
|
||
/// 天气组件视图模型
|
||
/// </summary>
|
||
public partial class WeatherComponentViewModel : ObservableObject
|
||
{
|
||
[ObservableProperty]
|
||
private WeatherComponent _component;
|
||
|
||
public WeatherComponentViewModel(WeatherComponent component)
|
||
{
|
||
_component = component;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新命令
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private async Task RefreshAsync()
|
||
{
|
||
// 强制刷新天气数据
|
||
await Component.UpdateAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置命令
|
||
/// </summary>
|
||
[RelayCommand]
|
||
private void Settings()
|
||
{
|
||
// 打开组件设置对话框
|
||
// 实际实现需要调用宿主的对话框服务
|
||
Component.Logger.LogInformation("Settings clicked");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤 4: 注册组件
|
||
|
||
在插件入口注册组件:
|
||
|
||
```csharp
|
||
public class Plugin : IPlugin
|
||
{
|
||
public async Task InitializeAsync(IPluginContext context)
|
||
{
|
||
var componentRegistry = context.Services
|
||
.GetService<IComponentRegistry>();
|
||
|
||
if (componentRegistry != null)
|
||
{
|
||
// 注册天气组件
|
||
componentRegistry.RegisterComponent<WeatherComponent>(
|
||
componentFactory: () => new WeatherComponent(),
|
||
viewFactory: (component) => new WeatherComponentView
|
||
{
|
||
DataContext = new WeatherComponentViewModel(
|
||
(WeatherComponent)component
|
||
)
|
||
}
|
||
);
|
||
|
||
context.Logger.LogInformation("WeatherComponent registered");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## ComponentBase API
|
||
|
||
### 核心属性
|
||
|
||
```csharp
|
||
public abstract class ComponentBase : ObservableObject, IComponent
|
||
{
|
||
// === 标识属性 ===
|
||
|
||
/// <summary>
|
||
/// 组件唯一标识符
|
||
/// </summary>
|
||
public abstract string Id { get; }
|
||
|
||
/// <summary>
|
||
/// 组件显示名称
|
||
/// </summary>
|
||
public abstract string Name { get; }
|
||
|
||
// === 服务访问 ===
|
||
|
||
/// <summary>
|
||
/// 日志记录器
|
||
/// </summary>
|
||
protected ILogger Logger { get; }
|
||
|
||
/// <summary>
|
||
/// 设置服务
|
||
/// </summary>
|
||
protected IComponentSettings Settings { get; }
|
||
|
||
/// <summary>
|
||
/// 服务提供者
|
||
/// </summary>
|
||
protected IServiceProvider Services { get; }
|
||
|
||
// === 生命周期方法 ===
|
||
|
||
/// <summary>
|
||
/// 组件初始化(创建时调用一次)
|
||
/// </summary>
|
||
public virtual Task InitializeAsync() => Task.CompletedTask;
|
||
|
||
/// <summary>
|
||
/// 组件更新(定时调用,默认1秒)
|
||
/// </summary>
|
||
public virtual Task UpdateAsync() => Task.CompletedTask;
|
||
|
||
/// <summary>
|
||
/// 组件销毁(清理资源)
|
||
/// </summary>
|
||
public virtual void Dispose() { }
|
||
}
|
||
```
|
||
|
||
### 辅助方法
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// 设置属性值并触发通知
|
||
/// </summary>
|
||
protected bool SetProperty<T>(
|
||
ref T field,
|
||
T value,
|
||
[CallerMemberName] string? propertyName = null)
|
||
{
|
||
if (EqualityComparer<T>.Default.Equals(field, value))
|
||
return false;
|
||
|
||
field = value;
|
||
OnPropertyChanged(propertyName);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发属性变更通知
|
||
/// </summary>
|
||
protected void OnPropertyChanged(
|
||
[CallerMemberName] string? propertyName = null)
|
||
{
|
||
PropertyChanged?.Invoke(
|
||
this,
|
||
new PropertyChangedEventArgs(propertyName)
|
||
);
|
||
}
|
||
```
|
||
|
||
## 组件生命周期
|
||
|
||
### 完整生命周期
|
||
|
||
```
|
||
1. 用户添加组件
|
||
↓
|
||
2. ComponentRegistry.CreateInstance()
|
||
├─ 调用 componentFactory()
|
||
├─ 创建组件实例
|
||
└─ 注入依赖(Logger, Settings, Services)
|
||
↓
|
||
3. 调用 InitializeAsync()
|
||
├─ 加载设置
|
||
├─ 初始化数据
|
||
└─ 订阅事件
|
||
↓
|
||
4. ComponentRegistry.CreateView()
|
||
├─ 调用 viewFactory()
|
||
├─ 创建视图
|
||
└─ 设置 DataContext
|
||
↓
|
||
5. 添加到桌面
|
||
├─ 包装到 DesktopWidgetWindow
|
||
├─ 设置位置和大小
|
||
└─ 显示窗口
|
||
↓
|
||
6. 定时更新循环
|
||
├─ 每 1 秒(可配置)
|
||
├─ 调用 UpdateAsync()
|
||
└─ UI 自动刷新(数据绑定)
|
||
↓
|
||
7. 用户移除组件 / 应用关闭
|
||
↓
|
||
8. 调用 Dispose()
|
||
├─ 取消订阅
|
||
├─ 保存状态
|
||
└─ 释放资源
|
||
↓
|
||
9. 从桌面移除
|
||
└─ 关闭窗口
|
||
```
|
||
|
||
### 更新频率控制
|
||
|
||
```csharp
|
||
public class MyComponent : ComponentBase
|
||
{
|
||
private DateTime _lastUpdate;
|
||
private readonly TimeSpan _updateInterval = TimeSpan.FromMinutes(5);
|
||
|
||
public override async Task UpdateAsync()
|
||
{
|
||
// 控制更新频率
|
||
if (DateTime.Now - _lastUpdate < _updateInterval)
|
||
return;
|
||
|
||
await FetchDataAsync();
|
||
_lastUpdate = DateTime.Now;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 组件设置
|
||
|
||
### 使用设置服务
|
||
|
||
```csharp
|
||
public class MyComponent : ComponentBase
|
||
{
|
||
public override Task InitializeAsync()
|
||
{
|
||
// 读取设置(带默认值)
|
||
var city = Settings.GetValue("City", "北京");
|
||
var refreshRate = Settings.GetValue("RefreshRate", 10);
|
||
var enabled = Settings.GetValue("Enabled", true);
|
||
|
||
// 读取复杂对象
|
||
var config = Settings.GetValue<MyConfig>("Config", new MyConfig());
|
||
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
public void SaveCity(string city)
|
||
{
|
||
// 保存设置
|
||
Settings.SetValue("City", city);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 监听设置变更
|
||
|
||
```csharp
|
||
public class MyComponent : ComponentBase
|
||
{
|
||
public override Task InitializeAsync()
|
||
{
|
||
// 监听设置变更
|
||
Settings.SettingChanged += OnSettingChanged;
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
private void OnSettingChanged(object? sender, SettingChangedEventArgs e)
|
||
{
|
||
if (e.Key == "City")
|
||
{
|
||
var newCity = e.NewValue as string;
|
||
// 响应城市变更
|
||
_ = FetchWeatherForCity(newCity);
|
||
}
|
||
}
|
||
|
||
public override void Dispose()
|
||
{
|
||
// 取消订阅
|
||
Settings.SettingChanged -= OnSettingChanged;
|
||
base.Dispose();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
### ✅ 性能优化
|
||
|
||
```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;
|
||
}
|
||
|
||
// ❌ 差:每次都重新获取
|
||
public async Task<string> GetDataAsync()
|
||
{
|
||
return await FetchDataAsync(); // 浪费资源
|
||
}
|
||
```
|
||
|
||
### ✅ 异步编程
|
||
|
||
```csharp
|
||
// ✅ 好:使用 async/await
|
||
public override async Task UpdateAsync()
|
||
{
|
||
await FetchDataAsync();
|
||
}
|
||
|
||
// ❌ 差:阻塞线程
|
||
public override Task UpdateAsync()
|
||
{
|
||
FetchDataAsync().Wait(); // 阻塞!
|
||
return Task.CompletedTask;
|
||
}
|
||
```
|
||
|
||
### ✅ 错误处理
|
||
|
||
```csharp
|
||
// ✅ 好:捕获并记录异常
|
||
public override async Task UpdateAsync()
|
||
{
|
||
try
|
||
{
|
||
await FetchDataAsync();
|
||
}
|
||
catch (HttpRequestException ex)
|
||
{
|
||
Logger.LogError(ex, "Network error");
|
||
DisplayError("网络错误");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.LogError(ex, "Unexpected error");
|
||
DisplayError("未知错误");
|
||
}
|
||
}
|
||
|
||
// ❌ 差:忽略异常
|
||
public override async Task UpdateAsync()
|
||
{
|
||
await FetchDataAsync(); // 异常会传播到宿主
|
||
}
|
||
```
|
||
|
||
### ✅ 资源管理
|
||
|
||
```csharp
|
||
// ✅ 好:正确释放资源
|
||
public class MyComponent : ComponentBase
|
||
{
|
||
private HttpClient? _httpClient;
|
||
private CancellationTokenSource? _cts;
|
||
|
||
public override void Dispose()
|
||
{
|
||
_cts?.Cancel();
|
||
_cts?.Dispose();
|
||
_httpClient?.Dispose();
|
||
base.Dispose();
|
||
}
|
||
}
|
||
|
||
// ❌ 差:不释放资源
|
||
public class MyComponent : ComponentBase
|
||
{
|
||
private HttpClient _httpClient = new(); // 内存泄漏
|
||
}
|
||
```
|
||
|
||
## 下一步
|
||
|
||
- [设置系统](03-设置系统.md) - 管理组件配置
|
||
- [主题与外观](04-主题外观.md) - 适配主题
|
||
- [ComponentBase API](../03-API参考/03-组件API.md) - API 详细文档
|
||
- [天气组件案例](../04-实战案例/01-天气组件.md) - 完整实战
|