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