diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..d7300de --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(ls -la \"/d/github/LanMountainDesktop/.claude/worktrees/agent-a4c5412322421ab67\" && ls -la \"/d/github/LanMountainDesktop\" && ls -la \"/d/github\")", + "Read(//d/github/**)" + ] + } +} diff --git a/LanMountainDesktop/Controls/CornerRadiusPreviewControl.cs b/LanMountainDesktop/Controls/CornerRadiusPreviewControl.cs new file mode 100644 index 0000000..bcf8a85 --- /dev/null +++ b/LanMountainDesktop/Controls/CornerRadiusPreviewControl.cs @@ -0,0 +1,101 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace LanMountainDesktop.Controls; + +public class CornerRadiusPreviewControl : Control +{ + public static readonly StyledProperty RadiusProperty = + AvaloniaProperty.Register(nameof(Radius), 24); + + public static readonly StyledProperty ShapeBrushProperty = + AvaloniaProperty.Register(nameof(ShapeBrush)); + + public static readonly StyledProperty GuideBrushProperty = + AvaloniaProperty.Register(nameof(GuideBrush)); + + public static readonly StyledProperty FillBrushProperty = + AvaloniaProperty.Register(nameof(FillBrush)); + + public double Radius + { + get => GetValue(RadiusProperty); + set => SetValue(RadiusProperty, value); + } + + public IBrush? ShapeBrush + { + get => GetValue(ShapeBrushProperty); + set => SetValue(ShapeBrushProperty, value); + } + + public IBrush? GuideBrush + { + get => GetValue(GuideBrushProperty); + set => SetValue(GuideBrushProperty, value); + } + + public IBrush? FillBrush + { + get => GetValue(FillBrushProperty); + set => SetValue(FillBrushProperty, value); + } + + static CornerRadiusPreviewControl() + { + AffectsRender( + RadiusProperty, + ShapeBrushProperty, + GuideBrushProperty, + FillBrushProperty); + } + + public override void Render(DrawingContext context) + { + var w = Bounds.Width; + var h = Bounds.Height; + if (w <= 0 || h <= 0) return; + + var shapeBrush = ShapeBrush ?? Brushes.Gray; + var guideBrush = GuideBrush ?? Brushes.Gray; + var fillBrush = FillBrush ?? new SolidColorBrush(Colors.Gray, 0.08); + + var padding = 24.0; + var maxShapeW = w - padding * 2; + var maxShapeH = h - padding * 2; + var shapeSize = Math.Min(maxShapeW, maxShapeH); + + if (shapeSize < 20) return; + + var ox = (w - shapeSize) / 2; + var oy = (h - shapeSize) / 2; + + var r = Math.Min(Radius, shapeSize * 0.45); + r = Math.Max(0, r); + + var shapeRect = new Rect(ox, oy, shapeSize, shapeSize); + var shapePen = new Pen(shapeBrush, 1.5); + var dashPen = new Pen(guideBrush, 0.75, new DashStyle([4, 3], 0)); + + context.DrawRectangle(fillBrush, shapePen, shapeRect, r, r); + + if (r > 4) + { + var arcCenterX = ox + r; + var arcCenterY = oy + r; + + context.DrawLine( + dashPen, + new Point(arcCenterX, oy + r * 0.2), + new Point(arcCenterX, oy + r * 0.9)); + + context.DrawLine( + dashPen, + new Point(ox + r * 0.2, arcCenterY), + new Point(ox + r * 0.9, arcCenterY)); + + context.DrawEllipse(null, new Pen(guideBrush, 0.75), new Point(arcCenterX, arcCenterY), 2, 2); + } + } +} diff --git a/LanMountainDesktop/Controls/GridPreviewControl.cs b/LanMountainDesktop/Controls/GridPreviewControl.cs new file mode 100644 index 0000000..00ca334 --- /dev/null +++ b/LanMountainDesktop/Controls/GridPreviewControl.cs @@ -0,0 +1,159 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace LanMountainDesktop.Controls; + +public class GridPreviewControl : Control +{ + public static readonly StyledProperty CellsProperty = + AvaloniaProperty.Register(nameof(Cells), 12); + + public static readonly StyledProperty AspectRatioProperty = + AvaloniaProperty.Register(nameof(AspectRatio), 16.0 / 9.0); + + public static readonly StyledProperty EdgeInsetPercentProperty = + AvaloniaProperty.Register(nameof(EdgeInsetPercent), 0); + + public static readonly StyledProperty GridBrushProperty = + AvaloniaProperty.Register(nameof(GridBrush)); + + public static readonly StyledProperty ScreenBorderBrushProperty = + AvaloniaProperty.Register(nameof(ScreenBorderBrush)); + + public static readonly StyledProperty InsetBrushProperty = + AvaloniaProperty.Register(nameof(InsetBrush)); + + public int Cells + { + get => GetValue(CellsProperty); + set => SetValue(CellsProperty, value); + } + + public double AspectRatio + { + get => GetValue(AspectRatioProperty); + set => SetValue(AspectRatioProperty, value); + } + + public int EdgeInsetPercent + { + get => GetValue(EdgeInsetPercentProperty); + set => SetValue(EdgeInsetPercentProperty, value); + } + + public IBrush? GridBrush + { + get => GetValue(GridBrushProperty); + set => SetValue(GridBrushProperty, value); + } + + public IBrush? ScreenBorderBrush + { + get => GetValue(ScreenBorderBrushProperty); + set => SetValue(ScreenBorderBrushProperty, value); + } + + public IBrush? InsetBrush + { + get => GetValue(InsetBrushProperty); + set => SetValue(InsetBrushProperty, value); + } + + static GridPreviewControl() + { + AffectsRender( + CellsProperty, + AspectRatioProperty, + EdgeInsetPercentProperty, + GridBrushProperty, + ScreenBorderBrushProperty, + InsetBrushProperty); + } + + public override void Render(DrawingContext context) + { + var w = Bounds.Width; + var h = Bounds.Height; + if (w <= 0 || h <= 0) return; + + var ratio = AspectRatio > 0 ? AspectRatio : 16.0 / 9.0; + double screenW, screenH; + if (w / h > ratio) + { + screenH = h; + screenW = h * ratio; + } + else + { + screenW = w; + screenH = w / ratio; + } + + var offsetX = (w - screenW) / 2; + var offsetY = (h - screenH) / 2; + + var borderBrush = ScreenBorderBrush ?? Brushes.Gray; + var gridBrush = GridBrush ?? Brushes.Gray; + var insetBrush = InsetBrush ?? new SolidColorBrush(Colors.Gray, 0.12); + + var borderThickness = 1.5; + var borderPen = new Pen(borderBrush, borderThickness); + + var cells = Math.Max(1, Cells); + var cellSize = screenW / cells; + var rows = (int)Math.Floor(screenH / cellSize); + + var insetPercent = Math.Clamp(EdgeInsetPercent, 0, 30); + var insetRatio = insetPercent / 100d; + var insetPx = Math.Min(cellSize * insetRatio, screenH * 0.15); + + var contentX = offsetX + insetPx; + var contentY = offsetY + insetPx; + var contentW = Math.Max(1, screenW - insetPx * 2); + var contentH = Math.Max(1, screenH - insetPx * 2); + + var screenRect = new Rect(offsetX, offsetY, screenW, screenH); + var contentRect = new Rect(contentX, contentY, contentW, contentH); + + var cornerRadius = Math.Min(6, Math.Min(screenW, screenH) * 0.03); + + using (context.PushClip(screenRect)) + { + if (insetPx > 0.5) + { + context.DrawRectangle(insetBrush, null, screenRect, cornerRadius, cornerRadius); + context.DrawRectangle( + new SolidColorBrush(Colors.Transparent), + new Pen(borderBrush, 0.75, new DashStyle([3, 3], 0)), + contentRect, + cornerRadius * 0.6, + cornerRadius * 0.6); + } + + var dashSegment = Math.Max(2, cellSize * 0.25); + var dashPen = new Pen(gridBrush, 1.0, new DashStyle([dashSegment, dashSegment], 0)); + + for (var col = 1; col < cells; col++) + { + var x = contentX + col * (contentW / cells); + if (x < contentX + contentW) + { + context.DrawLine(dashPen, new Point(x, contentY), new Point(x, contentY + contentH)); + } + } + + for (var row = 1; row < rows; row++) + { + var y = contentY + row * (contentH / rows); + if (y < contentY + contentH) + { + context.DrawLine(dashPen, new Point(contentX, y), new Point(contentX + contentW, y)); + } + } + } + + var adjustedRect = screenRect.Deflate(borderThickness / 2); + context.DrawRectangle(null, borderPen, adjustedRect, cornerRadius, cornerRadius); + } +} diff --git a/LanMountainDesktop/ViewModels/SettingsViewModels.cs b/LanMountainDesktop/ViewModels/SettingsViewModels.cs index 6cfc3bc..fd014ca 100644 --- a/LanMountainDesktop/ViewModels/SettingsViewModels.cs +++ b/LanMountainDesktop/ViewModels/SettingsViewModels.cs @@ -16,6 +16,7 @@ using LanMountainDesktop.Models; using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services; using LanMountainDesktop.Services.Settings; +using LanMountainDesktop.Appearance; using LanMountainDesktop.Settings.Core; using LanMountainDesktop.Shared.Contracts.Launcher; @@ -887,6 +888,9 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase [ObservableProperty] private int _shortSideCells; + [ObservableProperty] + private double _screenAspectRatio = 16.0 / 9.0; + [ObservableProperty] private int _edgeInsetPercent; @@ -914,6 +918,9 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _cornerRadiusStyle = GlobalAppearanceSettings.DefaultCornerRadiusStyle; + [ObservableProperty] + private double _cornerRadiusPreviewValue = 24; + [ObservableProperty] private IReadOnlyList _cornerRadiusStyleOptions = []; @@ -947,6 +954,7 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase SelectedCornerRadiusStyle = CornerRadiusStyleOptions.FirstOrDefault(option => string.Equals(option.Value, CornerRadiusStyle, StringComparison.OrdinalIgnoreCase)) ?? CornerRadiusStyleOptions.FirstOrDefault(o => o.Value == GlobalAppearanceSettings.DefaultCornerRadiusStyle); + CornerRadiusPreviewValue = AppearanceCornerRadiusTokenFactory.Create(CornerRadiusStyle).Component.TopLeft; } partial void OnShortSideCellsChanged(int value) @@ -987,6 +995,7 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase } CornerRadiusStyle = value.Value; + CornerRadiusPreviewValue = AppearanceCornerRadiusTokenFactory.Create(value.Value).Component.TopLeft; SaveComponentCornerRadius(); } diff --git a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml index 788beb4..3c4c6e2 100644 --- a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml @@ -1,4 +1,4 @@ - + + + + + + + + diff --git a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml.cs b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml.cs index f553ed1..b5a344e 100644 --- a/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml.cs +++ b/LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml.cs @@ -1,3 +1,6 @@ +using System.Linq; +using Avalonia.Controls; +using Avalonia.Platform; using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services.Settings; using LanMountainDesktop.ViewModels; @@ -24,6 +27,26 @@ public partial class ComponentsSettingsPage : SettingsPageBase ViewModel = viewModel; DataContext = ViewModel; InitializeComponent(); + InitScreenAspectRatio(); + } + + private void InitScreenAspectRatio() + { + try + { + var topLevel = TopLevel.GetTopLevel(this); + if (topLevel is null) return; + + var screen = topLevel.Screens.Primary ?? topLevel.Screens.All.FirstOrDefault(); + if (screen is not null && screen.Bounds.Height > 0) + { + ViewModel.ScreenAspectRatio = (double)screen.Bounds.Width / screen.Bounds.Height; + } + } + catch + { + // 无法获取屏幕信息时保持默认 16:9 + } } public ComponentsSettingsPageViewModel ViewModel { get; } diff --git a/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml index c6b6d51..7b083a4 100644 --- a/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml @@ -28,77 +28,74 @@ - - - - - - - - - - - + + + + Opacity="0.65" /> + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml.cs b/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml.cs index f8c6cbb..a73c4e8 100644 --- a/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml.cs +++ b/LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml.cs @@ -55,7 +55,8 @@ public partial class DataSettingsPage : SettingsPageBase return; } - if (string.Equals(e.PropertyName, nameof(DataSettingsPageViewModel.HasData), StringComparison.Ordinal)) + if (string.Equals(e.PropertyName, nameof(DataSettingsPageViewModel.HasData), StringComparison.Ordinal) || + string.Equals(e.PropertyName, nameof(DataSettingsPageViewModel.DiskUsagePercentage), StringComparison.Ordinal)) { RebuildStorageBar(); } @@ -80,43 +81,31 @@ public partial class DataSettingsPage : SettingsPageBase StorageBarGrid.ColumnDefinitions.Clear(); StorageBarGrid.Children.Clear(); - if (!ViewModel.HasData) - { - return; - } - var visibleItems = ViewModel.Items .Where(item => item.Percentage > 0) .OrderByDescending(item => item.Percentage) .ToList(); - if (visibleItems.Count == 0) - { - return; - } - - var totalPercent = visibleItems.Sum(item => item.Percentage); - if (totalPercent <= 0) - { - return; - } - var idx = 0; foreach (var item in visibleItems) { - var normalized = Math.Max(0.1, item.Percentage / totalPercent * 100d); - StorageBarGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(normalized, GridUnitType.Star))); - + var width = Math.Max(0.1, item.Percentage); + StorageBarGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(width, GridUnitType.Star))); var segment = new Border { Background = ParseBrush(item.ColorHex), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch }; - Grid.SetColumn(segment, idx++); StorageBarGrid.Children.Add(segment); } + + var remaining = 100d - ViewModel.DiskUsagePercentage; + if (remaining > 0) + { + StorageBarGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(remaining, GridUnitType.Star))); + } } private IBrush ParseBrush(string? hex) diff --git a/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml index c0a2455..8ee66d9 100644 --- a/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml @@ -1,58 +1,21 @@ - - - - - - + + - - - - - - - - - - - - - - @@ -71,11 +34,16 @@ - + + + + + diff --git a/SECURITY_AUDIT_REPORT.md b/SECURITY_AUDIT_REPORT.md new file mode 100644 index 0000000..a6ab7bd --- /dev/null +++ b/SECURITY_AUDIT_REPORT.md @@ -0,0 +1,196 @@ +# 安全审计报告 + +**项目**: LanMountainDesktop +**审计日期**: 2026-05-11 +**审计范围**: 整体代码库安全性评估 +**审计方法**: 自动化静态代码分析 + 架构审查 + +--- + +## 执行摘要 + +本次审计对 LanMountainDesktop 代码库进行了系统性安全评估,重点关注认证与访问控制、注入向量、外部交互以及敏感数据处理等高风险攻击面。 + +**审计结论**: 发现 **4 个已确认的中等及以上严重度漏洞**,建议立即修复。 + +--- + +## 已确认漏洞 + +### 漏洞 #1 - PostHog API Key 硬编码(高严重度) + +| 属性 | 详情 | +|------|------| +| **严重度** | 高 | +| **CWE** | CWE-798 - 使用硬编码凭证 | +| **位置** | `LanMountainDesktop/Services/PostHogUsageTelemetryService.cs:14` | +| **攻击者画像** | 源代码仓库的任何访问者(包括外部攻击者通过代码泄露或供应链攻击) | +| **可控输入** | 无(静态硬编码密钥) | + +**代码路径**: +```csharp +// PostHogUsageTelemetryService.cs:14 +private const string PostHogApiKey = "phc_bhQZvKDDfsEdLT6kkRFvrWMT8Pc5aCGGsnxoc5ijSf9"; +``` + +**影响**: +- 攻击者可能滥用此 API Key 向 PostHog 项目发送伪造遥测数据 +- 可能导致遥测数据污染或服务滥用 +- API Key 暴露在公开仓库中,任何人都能获取 + +**修复建议**: +```csharp +private const string PostHogApiKey = Environment.GetEnvironmentVariable("POSTHOG_API_KEY") + ?? throw new InvalidOperationException("PostHog API key not configured."); +``` + +--- + +### 漏洞 #2 - Sentry DSN 硬编码(高严重度) + +| 属性 | 详情 | +|------|------| +| **严重度** | 高 | +| **CWE** | CWE-798 - 使用硬编码凭证 | +| **位置** | `LanMountainDesktop/Services/SentryCrashTelemetryService.cs:15` | +| **攻击者画像** | 源代码仓库的任何访问者 | +| **可控输入** | 无(静态硬编码密钥) | + +**代码路径**: +```csharp +// SentryCrashTelemetryService.cs:15 +private const string SentryDsn = "https://f2aad3a1c63b5f2213ad82683ce93c06@o4511049423257600.ingest.us.sentry.io/4511049425813504"; +``` + +**影响**: +- Sentry DSN 等同于项目的访问凭证 +- 攻击者可利用此 DSN 向项目发送伪造崩溃报告 +- 可能导致崩溃数据污染或敏感信息收集 + +**修复建议**: +```csharp +private const string SentryDsn = Environment.GetEnvironmentVariable("SENTRY_DSN") + ?? throw new InvalidOperationException("Sentry DSN not configured."); +``` + +--- + +### 漏洞 #3 - 小米天气 API 签名密钥硬编码(高严重度) + +| 属性 | 详情 | +|------|------| +| **严重度** | 高 | +| **CWE** | CWE-798 - 使用硬编码凭证 | +| **位置** | `LanMountainDesktop/Services/XiaomiWeatherService.cs:25` | +| **攻击者画像** | 源代码仓库的任何访问者 | +| **可控输入** | 无(静态硬编码密钥) | + +**代码路径**: +```csharp +// XiaomiWeatherService.cs:25 +public string Sign { get; init; } = "zUFJoAR2ZVrDy1vF3D07"; +``` + +**影响**: +- 第三方 API 凭证暴露在公开仓库 +- 可能导致天气服务被滥用 +- 如密钥有权限限制,攻击者可能突破限制 + +**修复建议**: +```csharp +public string Sign { get; init; } = Environment.GetEnvironmentVariable("XIAOMI_WEATHER_SIGN") ?? ""; +``` + +--- + +### 漏洞 #4 - Sentry PII 收集配置(中等严重度) + +| 属性 | 详情 | +|------|------| +| **严重度** | 中等 | +| **CWE** | CWE-359 - 个人身份信息(PII)意外暴露 | +| **位置** | `LanMountainDesktop/Services/SentryCrashTelemetryService.cs:212` | +| **攻击者画像** | Sentry 后端管理员、内部威胁或数据泄露事件 | +| **可控输入** | 用户环境的机器名、用户名等系统信息 | +| **利用路径** | `程序启动 → TelemetryIdentityService.Initialize()` → 遥测数据上报 | + +**代码路径**: +```csharp +// SentryCrashTelemetryService.cs:212 +options.SendDefaultPii = true; +``` + +**影响**: +- `SendDefaultPii = true` 配置会收集和上报用户 IP 地址 +- 可能违反隐私法规(如 GDPR)要求 +- 在崩溃报告中可能暴露用户敏感信息 + +**修复建议**: +```csharp +options.SendDefaultPii = false; // 默认收集 PII +options.SendDefaultPii = TelemetryEnvironmentInfo.IsTelemetryPiiAllowed(); // 或根据用户同意状态动态设置 +``` + +--- + +## 未发现漏洞的区域 + +经过系统性审计,以下区域未发现中等及以上严重度的已确认漏洞: + +### 认证与访问控制 +- 单实例服务实现正确(使用互斥体) +- IPC 通信使用命名管道,无明显认证绕过风险 +- 插件隔离使用独立进程边界 + +### 注入向量 +- SQLite 使用参数化查询,无 SQL 注入风险 +- JSON 反序列化使用强类型上下文,无反序列化漏洞 +- 文件路径操作使用 `Path.Combine`,有基本的路径遍历防护 +- 未发现命令执行注入 + +### 外部交互 +- HTTP 请求正确使用 `HttpClient` 和超时配置 +- Webhook/回调 URL 使用 `Uri.EscapeDataString` 编码 +- 下载服务验证目标路径,无路径遍历风险 + +### 敏感数据处理 +- 数据库本地存储,使用 WAL 模式 +- 设置数据通过 JSON 序列化存储在用户目录 +- 日志文件路径正确隔离在应用数据目录 + +--- + +## 架构安全评估 + +| 组件 | 安全评级 | 说明 | +|------|----------|------| +| 插件系统 | 良好 | 使用独立进程隔离 | +| IPC 通信 | 良好 | 命名管道通信,进程边界隔离 | +| 更新系统 | 良好 | 支持签名验证 | +| 遥测系统 | **需改进** | 存在硬编码凭证和 PII 配置问题 | +| 数据存储 | 良好 | 使用标准加密实践 | + +--- + +## 修复优先级 + +| 优先级 | 漏洞 | 预计工作量 | +|--------|------|------------| +| P0 - 紧急 | #1 PostHog API Key | 低 | +| P0 - 紧急 | #2 Sentry DSN | 低 | +| P0 - 紧急 | #3 Xiaomi Weather Sign | 低 | +| P1 - 高 | #4 SendDefaultPii | 低 | + +--- + +## 建议的安全改进 + +1. **实施密钥管理**: 使用环境变量或密钥管理服务(如 Azure Key Vault、AWS Secrets Manager)存储所有 API 凭证 +2. **添加密钥扫描**: 在 CI/CD 流程中集成 secrets scanning(如 GitGuardian、trufflehog) +3. **隐私合规审查**: 确认遥测数据收集符合当地隐私法规要求 +4. **代码审计**: 建议进行定期安全审计 + +--- + +*报告生成工具: 自动安全审计系统* +*审计方法: 静态代码分析 + 架构审查* diff --git a/docs/auto_commit_md/20260511_SUMMARY.md b/docs/auto_commit_md/20260511_SUMMARY.md new file mode 100644 index 0000000..9e7c300 --- /dev/null +++ b/docs/auto_commit_md/20260511_SUMMARY.md @@ -0,0 +1,57 @@ +# Git 提交分析报告 - 2026-05-11 + +## 摘要 + +**日期**: 2026-05-11 +**新提交数量**: 0 +**状态**: ⚠️ 无新提交 + +--- + +## 详细说明 + +今天(2026-05-11)没有新的 Git 提交记录。 + +### 最近一次提交信息 + +- **提交哈希**: `d8f75e86be9054b29303dec01ec434ccb4db2b7f` +- **作者**: lincube +- **提交时间**: 2026-05-07 21:39:21 +0800 +- **提交信息**: Add IPC backoff/retries and safer disposal + +### 仓库状态 + +当前分支:`setting` +分支状态:与 `origin/setting` 保持同步 + +### 待提交更改 + +当前工作目录中存在以下未提交的更改: + +**已修改文件**: +- LanMountainDesktop/ViewModels/SettingsViewModels.cs +- LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml +- LanMountainDesktop/Views/SettingsPages/ComponentsSettingsPage.axaml.cs +- LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml +- LanMountainDesktop/Views/SettingsPages/DataSettingsPage.axaml.cs +- LanMountainDesktop/Views/SettingsPages/LauncherSettingsPage.axaml + +**未跟踪文件**: +- LanMountainDesktop/Controls/CornerRadiusPreviewControl.cs +- LanMountainDesktop/Controls/GridPreviewControl.cs +- SECURITY_AUDIT_REPORT.md +- mockup-noise-level.html + +--- + +## 下一步建议 + +1. 如果有未提交的更改,请先提交 +2. 推送更改到远程仓库 +3. 明天再次运行此分析脚本 + +## 报告生成信息 + +- **生成时间**: 2026-05-11 +- **分析工具**: Git Commit Analyzer +- **输出目录**: docs/auto_commit_md/ diff --git a/mockup-noise-level.html b/mockup-noise-level.html new file mode 100644 index 0000000..1cb7f24 --- /dev/null +++ b/mockup-noise-level.html @@ -0,0 +1,898 @@ + + + + + +噪音等级组件改造 Mockup v2 + + + + + + +
+ 设计理念:不是整张图都铺满渐变色带,而是只在当前等级附近产生一个柔和的聚光灯光晕。 + 其余区域保持低调暗淡,让用户视线自然聚焦到当前所处等级。 + 当等级切换时,光晕平滑滑动到新位置,颜色同步过渡。 +

+ Quiet 安静 → + Normal 正常 → + Noisy 嘈杂 → + Extreme 极端 +  点击下方按钮切换等级,观察光晕移动效果 +
+ + +
+
🎛 模拟噪音等级切换
+
+
+
当前等级
+
+ + + + +
+
+
+
+ + +
+
当前实现 (Baseline)
+
四色硬切色带,各等级区域均匀着色,没有视觉焦点。用户需要主动寻找"我在哪个等级"。
+
+
+
+ 噪音等级分布 + Realtime +
+
+
+
+
+
+
+
+
+ Extreme + Noisy + Normal + Quiet +
+
+ + + + + + + + + + + + + + +
+
+ -12s + -6s + Now +
+
+
+
+
+ + +
+
聚光灯方案 — 局部渐变聚焦
+
只在当前等级附近产生柔和光晕,其余区域保持暗淡。等级切换时光晕平滑滑动,Y轴标签联动高亮,右侧指示条标注当前位置。下方条形图展示分布占比。
+
+ +
+
推荐
+
+ 噪音等级分布 + Realtime +
+
+ +
+
+
+ + +
+
+
+
+ + +
+ Extreme + Noisy + Normal + Quiet +
+ + +
+
+ + +
+ + + + + + + + + + + + + + +
+ +
+ -12s + -6s + Now +
+
+ + +
+
等级分布
+
+
+
+
+
+
+
+ 安静 35% + 正常 40% + 嘈杂 18% + 极端 7% +
+
+
+ + +
+
紧凑模式
+
+ 噪音等级分布 + Session +
+
+
+
+
+ +
+
+
+
+ +
+ Ext + Noisy + Norm + Quiet +
+ +
+
+ +
+ + + + +
+ +
+ -12s + -6s + Now +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + +
+
四种等级状态一览
+
同时展示四个等级的聚光灯效果,可以直观对比光晕位置、颜色和强度的差异。
+
+ +
+
● Quiet 安静
+
+
+
+
+
+
+
+
+ Ext + Noisy + Norm + Quiet +
+
+
+
+ + + + + +
+
+
+ +
+
● Normal 正常
+
+
+
+
+
+
+
+
+ Ext + Noisy + Norm + Quiet +
+
+
+
+ + + + + +
+
+
+ +
+
● Noisy 嘈杂
+
+
+
+
+
+
+
+
+ Ext + Noisy + Norm + Quiet +
+
+
+
+ + + + + +
+
+
+ +
+
● Extreme 极端
+
+
+
+
+
+
+
+
+ Ext + Noisy + Norm + Quiet +
+
+
+
+ + + + + +
+
+
+
+
+ +@keyframes extremePulse { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + + + + +