diff --git a/LanMountainDesktop/Assets/Documents/Privacy.md b/LanMountainDesktop/Assets/Documents/Privacy.md index 8d0d827..8ebf5b8 100644 --- a/LanMountainDesktop/Assets/Documents/Privacy.md +++ b/LanMountainDesktop/Assets/Documents/Privacy.md @@ -1,6 +1,6 @@ # LanMountainDesktop 隐私政策 -**最后更新日期:2024年** +**最后更新日期:2026年3月17日** --- @@ -321,6 +321,6 @@ a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 --- -**感谢您信任 LanMountainDesktop!** +**感谢您信任阑山桌面LanMountainDesktop!** 我们承诺保护您的隐私,并持续改进我们的隐私保护措施。 diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj index 3b616f8..c369c61 100644 --- a/LanMountainDesktop/LanMountainDesktop.csproj +++ b/LanMountainDesktop/LanMountainDesktop.csproj @@ -1,4 +1,4 @@ - + WinExe net10.0 @@ -21,7 +21,9 @@ + + diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json index b5302e7..8a4f8b9 100644 --- a/LanMountainDesktop/Localization/en-US.json +++ b/LanMountainDesktop/Localization/en-US.json @@ -559,6 +559,7 @@ "component_category.info": "Info", "component_category.calculator": "Calculator", "component_category.study": "Study", + "component_category.file": "File", "component.date": "Calendar", "component.month_calendar": "Month Calendar", "component.lunar_calendar": "Lunar Calendar", @@ -892,5 +893,7 @@ "placement.tile": "Tile", "single_instance.notice.title": "App already running", "single_instance.notice.description": "The app is already running. There is no need to click multiple times to open it.", - "single_instance.notice.button": "OK" + "single_instance.notice.button": "OK", + "market.status.install_success_restart_format": "✓ Plugin '{0}' installed successfully! Please restart the application to activate it.", + "market.dialog.restart_message_format": "Plugin '{0}' has been installed successfully.\n\nTo use this plugin, you need to restart the application now.\n\nWould you like to restart?" } diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index d849bd6..811a6b5 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -557,6 +557,7 @@ "component_category.info": "信息推荐", "component_category.calculator": "计算器", "component_category.study": "自习", + "component_category.file": "文件", "component.date": "日历", "component.month_calendar": "月历", "component.lunar_calendar": "农历", @@ -890,5 +891,7 @@ "placement.tile": "平铺", "single_instance.notice.title": "应用已经运行", "single_instance.notice.description": "应用已经运行,无需多次点击打开。", - "single_instance.notice.button": "确定" + "single_instance.notice.button": "确定", + "market.status.install_success_restart_format": "✓ 插件"{0}"安装成功!请重启应用以激活它。", + "market.dialog.restart_message_format": "插件"{0}"已成功安装。\n\n要使用此插件,您需要立即重启应用。\n\n是否立即重启?" } diff --git a/LanMountainDesktop/Services/LocalizationService.cs b/LanMountainDesktop/Services/LocalizationService.cs index bf56313..f46e7b7 100644 --- a/LanMountainDesktop/Services/LocalizationService.cs +++ b/LanMountainDesktop/Services/LocalizationService.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Text.Json; namespace LanMountainDesktop.Services; @@ -42,11 +43,14 @@ public sealed class LocalizationService var result = new Dictionary(StringComparer.OrdinalIgnoreCase); try { - var filePath = Path.Combine(AppContext.BaseDirectory, "Localization", $"{languageCode}.json"); - if (File.Exists(filePath)) + var json = TryLoadFromFileSystem(languageCode); + if (string.IsNullOrEmpty(json)) + { + json = TryLoadFromEmbeddedResource(languageCode); + } + + if (!string.IsNullOrEmpty(json)) { - var json = File.ReadAllText(filePath); - // Defensive: tolerate accidentally duplicated UTF-8 BOM characters at file start. json = json.TrimStart('\uFEFF'); var data = JsonSerializer.Deserialize>(json, JsonOptions); if (data is not null) @@ -63,4 +67,41 @@ public sealed class LocalizationService _cache[languageCode] = result; return result; } + + private string? TryLoadFromFileSystem(string languageCode) + { + try + { + var filePath = Path.Combine(AppContext.BaseDirectory, "Localization", $"{languageCode}.json"); + if (File.Exists(filePath)) + { + return File.ReadAllText(filePath); + } + } + catch + { + // Continue to next method + } + return null; + } + + private string? TryLoadFromEmbeddedResource(string languageCode) + { + try + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"LanMountainDesktop.Localization.{languageCode}.json"; + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream != null) + { + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + } + catch + { + // Continue to next method + } + return null; + } } diff --git a/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs b/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs index 344401e..5a5394b 100644 --- a/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs +++ b/LanMountainDesktop/Services/OfficeRecentDocumentsService.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using LanMountainDesktop.Services.Settings; +using Microsoft.Win32; namespace LanMountainDesktop.Services; @@ -35,66 +36,19 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService public List GetRecentDocuments(int maxCount = 20) { var documents = new List(); - var recentPaths = GetRecentFolders(); - foreach (var recentPath in recentPaths) - { - if (!Directory.Exists(recentPath)) - { - continue; - } + // 方法1: 从注册表读取Office最近文档(最可靠) + TryGetFromRegistry(documents); - try - { - var files = Directory.GetFiles(recentPath, "*.lnk"); - foreach (var lnkPath in files) - { - var targetPath = GetShortcutTarget(lnkPath); - if (string.IsNullOrEmpty(targetPath)) - { - continue; - } + // 方法2: 从Recent文件夹读取快捷方式(备用) + TryGetFromRecentFolders(documents); - var extension = Path.GetExtension(targetPath).ToLowerInvariant(); - if (!IsOfficeFile(extension)) - { - continue; - } - - if (!System.IO.File.Exists(targetPath)) - { - continue; - } - - try - { - var fileInfo = new FileInfo(targetPath); - var doc = new OfficeRecentDocument - { - FileName = Path.GetFileNameWithoutExtension(targetPath), - FilePath = targetPath, - Extension = extension, - LastModifiedTime = fileInfo.LastWriteTime, - FileSizeBytes = fileInfo.Length, - IconGlyph = GetIconGlyph(extension) - }; - - if (!documents.Any(d => d.FilePath == targetPath)) - { - documents.Add(doc); - } - } - catch - { - } - } - } - catch - { - } - } + // 方法3: 从Windows Jump List读取(如果可用) + TryGetFromJumpList(documents); return documents + .GroupBy(d => d.FilePath, StringComparer.OrdinalIgnoreCase) + .Select(g => g.OrderByDescending(d => d.LastModifiedTime).First()) .OrderByDescending(d => d.LastModifiedTime) .Take(maxCount) .ToList(); @@ -116,6 +70,231 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService } } +#pragma warning disable CA1416 // 平台兼容性警告 + private void TryGetFromRegistry(List documents) + { + try + { + // Word最近文档 + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\Word\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Word\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\Word\Reading Locations"); + + // Excel最近文档 + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\Excel\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Excel\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\Excel\Reading Locations"); + + // PowerPoint最近文档 + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\PowerPoint\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\PowerPoint\Reading Locations"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\PowerPoint\Reading Locations"); + + // 通用Office最近文档 + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office Word"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office Excel"); + TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office PowerPoint"); + } + catch + { + // 忽略注册表访问错误 + } + } + + private void TryGetFromOfficeRegistry(List documents, string registryPath) + { + try + { + using var key = Registry.CurrentUser.OpenSubKey(registryPath); + if (key == null) return; + + foreach (var subKeyName in key.GetSubKeyNames()) + { + try + { + using var subKey = key.OpenSubKey(subKeyName); + if (subKey == null) continue; + + var filePath = subKey.GetValue("Path") as string; + if (string.IsNullOrEmpty(filePath)) continue; + + AddDocumentIfExists(documents, filePath); + } + catch + { + // 忽略单个子键访问错误 + } + } + } + catch + { + // 忽略注册表访问错误 + } + } +#pragma warning restore CA1416 // 平台兼容性警告 + + private void TryGetFromRecentFolders(List documents) + { + var recentPaths = GetRecentFolders(); + + foreach (var recentPath in recentPaths) + { + if (!Directory.Exists(recentPath)) + { + continue; + } + + try + { + var files = Directory.GetFiles(recentPath, "*.lnk"); + foreach (var lnkPath in files) + { + var targetPath = GetShortcutTarget(lnkPath); + if (string.IsNullOrEmpty(targetPath)) + { + continue; + } + + AddDocumentIfExists(documents, targetPath); + } + } + catch + { + // 忽略文件夹访问错误 + } + } + } + + private void TryGetFromJumpList(List documents) + { + try + { + // Windows Jump List存储在以下位置 + var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var jumpListPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Microsoft", "Windows", "Recent", "AutomaticDestinations"); + + if (!Directory.Exists(jumpListPath)) return; + + // Office应用的Jump List文件 + var officeJumpListFiles = new[] + { + "a7bd7a3f3d5a4c74.automaticDestinations-ms", // Word + "9b524fe3be704a4d.automaticDestinations-ms", // Excel + "d0063c4c7de64e5e.automaticDestinations-ms" // PowerPoint + }; + + foreach (var jumpFile in officeJumpListFiles) + { + var fullPath = Path.Combine(jumpListPath, jumpFile); + if (File.Exists(fullPath)) + { + TryParseJumpListFile(fullPath, documents); + } + } + } + catch + { + // Jump List解析失败,忽略 + } + } + + private void TryParseJumpListFile(string jumpListPath, List documents) + { + try + { + // Jump List文件是二进制格式,这里使用简化的方法 + // 读取文件并尝试提取文件路径 + var bytes = File.ReadAllBytes(jumpListPath); + var text = Encoding.Unicode.GetString(bytes); + + // 查找可能的文件路径(简化实现) + var possiblePaths = ExtractPossiblePaths(text); + foreach (var path in possiblePaths) + { + AddDocumentIfExists(documents, path); + } + } + catch + { + // Jump List解析失败,忽略 + } + } + + private IEnumerable ExtractPossiblePaths(string text) + { + var paths = new List(); + + // 查找常见的文件路径模式 + var patterns = new[] + { + @"[A-Z]:\\[^\x00-\x1F""<>|]*\.(docx?|xlsx?|pptx?|rtf|csv)", + @"\\\\[^\\]+\\[^\x00-\x1F""<>|]*\.(docx?|xlsx?|pptx?|rtf|csv)" + }; + + foreach (var pattern in patterns) + { + try + { + var matches = System.Text.RegularExpressions.Regex.Matches(text, pattern, + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + foreach (System.Text.RegularExpressions.Match match in matches) + { + var path = match.Value.Trim('\0', ' ', '"'); + if (!string.IsNullOrEmpty(path)) + { + paths.Add(path); + } + } + } + catch + { + // 忽略正则表达式错误 + } + } + + return paths.Distinct(StringComparer.OrdinalIgnoreCase); + } + + private void AddDocumentIfExists(List documents, string filePath) + { + try + { + var extension = Path.GetExtension(filePath).ToLowerInvariant(); + if (!IsOfficeFile(extension)) + { + return; + } + + if (!File.Exists(filePath)) + { + return; + } + + var fileInfo = new FileInfo(filePath); + var doc = new OfficeRecentDocument + { + FileName = Path.GetFileNameWithoutExtension(filePath), + FilePath = filePath, + Extension = extension, + LastModifiedTime = fileInfo.LastWriteTime, + FileSizeBytes = fileInfo.Length, + IconGlyph = GetIconGlyph(extension) + }; + + if (!documents.Any(d => string.Equals(d.FilePath, filePath, StringComparison.OrdinalIgnoreCase))) + { + documents.Add(doc); + } + } + catch + { + // 忽略单个文件处理错误 + } + } + private static List GetRecentFolders() { var folders = new List(); @@ -125,6 +304,12 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService folders.Add(Path.Combine(appData, "Microsoft", "Excel", "Recent")); folders.Add(Path.Combine(appData, "Microsoft", "PowerPoint", "Recent")); + // 添加Office 365路径 + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "Word", "Recent")); + folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "Excel", "Recent")); + folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "PowerPoint", "Recent")); + return folders; } diff --git a/LanMountainDesktop/ViewModels/PluginMarketSettingsPageViewModels.cs b/LanMountainDesktop/ViewModels/PluginMarketSettingsPageViewModels.cs index ed3b150..9a64931 100644 --- a/LanMountainDesktop/ViewModels/PluginMarketSettingsPageViewModels.cs +++ b/LanMountainDesktop/ViewModels/PluginMarketSettingsPageViewModels.cs @@ -564,11 +564,19 @@ public sealed partial class PluginMarketSettingsPageViewModel : ViewModelBase { RefreshInstalledSnapshot(); RefreshItemStates(); + + // 设置更明显的状态消息 + var pluginName = result.PluginName ?? item.Name; StatusMessage = string.Format( CultureInfo.CurrentCulture, - L("market.status.install_success_format", "Plugin '{0}' has been staged. Restart the app to apply it."), - result.PluginName ?? item.Name); - RestartRequested?.Invoke(RestartRequiredMessage); + L("market.status.install_success_restart_format", "✓ Plugin '{0}' installed successfully! Please restart the application to activate it."), + pluginName); + + // 触发重启提醒 + RestartRequested?.Invoke(string.Format( + CultureInfo.CurrentCulture, + L("market.dialog.restart_message_format", "Plugin '{0}' has been installed successfully.\n\nTo use this plugin, you need to restart the application now.\n\nWould you like to restart?"), + pluginName)); return; } diff --git a/LanMountainDesktop/ViewModels/SettingsViewModels.cs b/LanMountainDesktop/ViewModels/SettingsViewModels.cs index 0ac8640..ba6063a 100644 --- a/LanMountainDesktop/ViewModels/SettingsViewModels.cs +++ b/LanMountainDesktop/ViewModels/SettingsViewModels.cs @@ -268,12 +268,17 @@ public sealed partial class GeneralSettingsPageViewModel : ViewModelBase partial void OnSelectedLanguageChanged(SelectionOption value) { - RefreshPreview(); if (_isInitializing || value is null) { return; } + // 更新语言代码并刷新UI文本 + _languageCode = _localizationService.NormalizeLanguageCode(value.Value); + RefreshLocalizedText(); + RefreshPreview(); + + // 保存设置 _settingsFacade.Region.Save(new RegionSettingsState( value.Value, NormalizeTimeZoneId(SelectedTimeZone?.Id))); diff --git a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml index c0cb182..83b7450 100644 --- a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml +++ b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml @@ -43,7 +43,7 @@ + VerticalScrollBarVisibility="Auto"> diff --git a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs index 3660544..d4a81fa 100644 --- a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs @@ -198,12 +198,32 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, return; } - if (courseIndex < CourseListPanel.Children.Count) + // 确保在UI线程执行 + Avalonia.Threading.Dispatcher.UIThread.Post(() => { + if (courseIndex >= CourseListPanel.Children.Count) + { + return; + } + var targetChild = CourseListPanel.Children[courseIndex]; + if (targetChild == null || !targetChild.IsArrangeValid) + { + return; + } + var bounds = targetChild.Bounds; - ContentScrollViewer.Offset = new Vector(0, bounds.Position.Y); - } + var scrollViewerHeight = ContentScrollViewer.Bounds.Height; + var contentHeight = CourseListPanel.Bounds.Height; + + // 计算滚动位置,使当前课程居中显示 + var targetOffset = bounds.Position.Y - (scrollViewerHeight / 2) + (bounds.Height / 2); + + // 确保不超出边界 + targetOffset = Math.Max(0, Math.Min(targetOffset, contentHeight - scrollViewerHeight)); + + ContentScrollViewer.Offset = new Vector(0, targetOffset); + }, Avalonia.Threading.DispatcherPriority.Loaded); } public void RefreshFromSettings() @@ -298,6 +318,15 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, var currentIndex = FindCurrentCourseIndex(); _lastCurrentCourseIndex = currentIndex; HideStatus(); + + // 初始化时自动跳转到当前课程 + if (currentIndex >= 0) + { + Avalonia.Threading.Dispatcher.UIThread.Post(() => + { + ScrollToCurrentCourse(currentIndex); + }, Avalonia.Threading.DispatcherPriority.Loaded); + } } RenderScheduleItems(); @@ -484,10 +513,9 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, : CreateBrush("#FF4D5A"); var normalBulletBrush = CreateBrush(_isNightVisual ? "#B8BEC9" : "#9AA3B2"); - var visibleItems = _courseItems.Take(maxVisibleItems).ToList(); - for (var i = 0; i < visibleItems.Count; i++) + for (var i = 0; i < _courseItems.Count; i++) { - var item = visibleItems[i]; + var item = _courseItems[i]; var bulletBrush = item.IsCurrent ? currentBrush : normalBulletBrush; var bullet = new Border diff --git a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml index cfc050d..59d50c7 100644 --- a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml +++ b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml @@ -11,7 +11,7 @@ @@ -23,15 +23,15 @@ VerticalAlignment="Top" Margin="0,-40,-40,0" CornerRadius="70" - Background="{DynamicResource SystemAccentColorLight2Brush}" - Opacity="0.2" + Background="#4A90D9" + Opacity="0.3" IsHitTestVisible="False" /> @@ -48,7 +48,7 @@ PointerPressed="OnRefreshPointerPressed"> + Foreground="#B8FFFFFF" /> @@ -68,14 +68,14 @@ Width="130" Height="90" CornerRadius="10" - Background="{DynamicResource AdaptiveGlassPanelBackgroundBrush}" + Background="#3AFFFFFF" Padding="10" Cursor="Hand" PointerPressed="OnDocumentCardPointerPressed"> @@ -99,7 +99,7 @@ diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs index 3aa545e..2118bb4 100644 --- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs +++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs @@ -2702,6 +2702,11 @@ public partial class MainWindow return Symbol.Apps; } + if (string.Equals(categoryId, "File", StringComparison.OrdinalIgnoreCase)) + { + return Symbol.Folder; + } + return Symbol.Apps; } @@ -2747,6 +2752,11 @@ public partial class MainWindow return L("component_category.study", "Study"); } + if (string.Equals(categoryId, "File", StringComparison.OrdinalIgnoreCase)) + { + return L("component_category.file", "File"); + } + return categoryId; }