using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.Core.Script.Group; using BetterGenshinImpact.Core.Script.Project; using BetterGenshinImpact.GameTask.AutoPathing; using BetterGenshinImpact.GameTask.AutoPathing.Model; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Helpers.Ui; using BetterGenshinImpact.Model; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Controls.Drawer; using BetterGenshinImpact.View.Controls.Webview; using BetterGenshinImpact.View.Pages.View; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.ViewModel.Message; using BetterGenshinImpact.ViewModel.Pages.View; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.Logging; using Microsoft.Web.WebView2.Wpf; using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using Wpf.Ui.Violeta.Controls; using Wpf.Ui.Violeta.Win32; namespace BetterGenshinImpact.ViewModel.Pages; public partial class MapPathingViewModel : ViewModel { private readonly ILogger _logger = App.GetLogger(); public static readonly string PathJsonPath = Global.Absolute(@"User\AutoPathing"); [ObservableProperty] private ObservableCollection> _treeList = []; [ObservableProperty] private FileTreeNode? _selectNode; [ObservableProperty] private bool _isRightClickSelection; private MapPathingDevWindow? _mapPathingDevWindow; private readonly IScriptService _scriptService; public AllConfig Config { get; set; } // 添加抽屉ViewModel public DrawerViewModel DrawerVm { get; } = new DrawerViewModel(); /// public MapPathingViewModel(IScriptService scriptService, IConfigService configService) { _scriptService = scriptService; Config = configService.Get(); WeakReferenceMessenger.Default.Register(this, (r, m) => InitScriptListViewData()); IconManager.CacheExcludeExtensions = [".ico"]; } private void InitScriptListViewData() { TreeList.Clear(); var root = FileTreeNodeHelper.LoadDirectory(PathJsonPath); // 循环写入 root.Children foreach (var item in root.Children) { SetIconForNodeAndChildren(item); TreeList.Add(item); } } private void SetIconForNodeAndChildren(FileTreeNode node) { if (!string.IsNullOrEmpty(node.FilePath) && File.Exists(Path.Combine(node.FilePath, "icon.ico"))) { node.IconFilePath = Path.Combine(node.FilePath, "icon.ico"); } else { node.IconFilePath = node.FilePath; } foreach (var child in node.Children) { SetIconForNodeAndChildren(child); } } public override void OnNavigatedTo() { InitScriptListViewData(); } [RelayCommand] public void OnOpenScriptsFolder() { if (!Directory.Exists(PathJsonPath)) { Directory.CreateDirectory(PathJsonPath); } Process.Start("explorer.exe", PathJsonPath); } [RelayCommand] public void OnOpenScriptProjectFolder(ScriptProject? item) { if (item == null) { return; } Process.Start("explorer.exe", item.ProjectPath); } [RelayCommand] public async Task OnStart(FileTreeNode? item) { // 如果没有传入参数,使用选中的节点 if (item == null) { item = SelectNode; } if (item == null) { return; } if (item.IsDirectory) { Toast.Warning("执行多个地图追踪任务的时候,请使用调度器功能"); return; } if (string.IsNullOrEmpty(item.FilePath)) { return; } var fileInfo = new FileInfo(item.FilePath); var project = ScriptGroupProject.BuildPathingProject(fileInfo.Name, fileInfo.DirectoryName!); await _scriptService.RunMulti([project]); } [RelayCommand] public void OnOpenDevTools() { if (_mapPathingDevWindow == null || !_mapPathingDevWindow.IsVisible) { _mapPathingDevWindow = new MapPathingDevWindow(); _mapPathingDevWindow.Closed += (s, e) => _mapPathingDevWindow = null; _mapPathingDevWindow.Show(); } else { _mapPathingDevWindow.Activate(); } } [RelayCommand] public async void OnOpenSettings() { // var uiMessageBox = new Wpf.Ui.Controls.MessageBox // { // Content = view, // Title = "地图追踪配置", // CloseButtonText = "关闭" // }; // // await uiMessageBox.ShowDialogAsync(); var vm = App.GetService(); var view = new PathingConfigView(vm); view?.ShowDialog(); } [RelayCommand] public void OnGoToPathingUrl() { Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/pathing.html") { UseShellExecute = true }); } [RelayCommand] public void OnRefresh() { InitScriptListViewData(); } [RelayCommand] public async Task OnDelete(FileTreeNode? item) { // 如果没有传入参数,使用选中的节点 if (item == null) { item = SelectNode; } if (item == null) { return; } if (string.IsNullOrEmpty(item.FilePath)) { Toast.Warning("无法删除:路径无效"); return; } // 确定删除的内容类型 string itemType = item.IsDirectory ? "文件夹" : "文件"; string itemName = item.FileName ?? "未知项目"; // 显示确认对话框 var messageBox = new Wpf.Ui.Controls.MessageBox { Title = "删除确认", Content = $"确定要删除{itemType} \"{itemName}\" 吗?\n\n此操作将永久删除该{itemType}及其所有内容,无法恢复!", PrimaryButtonText = "删除", CloseButtonText = "取消", Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner, }; var result = await messageBox.ShowDialogAsync(); if (result == Wpf.Ui.Controls.MessageBoxResult.Primary) { try { if (item.IsDirectory) { // 删除目录 if (Directory.Exists(item.FilePath)) { Directory.Delete(item.FilePath, true); Toast.Success($"已删除文件夹: {itemName}"); _logger.LogInformation("已删除地图追踪文件夹: {Name} ({Path})", itemName, item.FilePath); } else { Toast.Warning("文件夹不存在"); } } else { // 删除文件 if (File.Exists(item.FilePath)) { File.Delete(item.FilePath); Toast.Success($"已删除文件: {itemName}"); _logger.LogInformation("已删除地图追踪文件: {Name} ({Path})", itemName, item.FilePath); } else { Toast.Warning("文件不存在"); } } // 刷新列表 InitScriptListViewData(); } catch (Exception ex) { Toast.Error($"删除失败: {ex.Message}"); _logger.LogError(ex, "删除地图追踪项失败"); } } } [RelayCommand] public void OnOpenLocalScriptRepo() { Config.ScriptConfig.ScriptRepoHintDotVisible = false; ScriptRepoUpdater.Instance.OpenScriptRepoWindow(); } [RelayCommand] private void SetRightClickSelection(string isRightClick) { IsRightClickSelection = "True".Equals(isRightClick, StringComparison.OrdinalIgnoreCase); } [RelayCommand] public void OnOpenPathingDetail() { var item = SelectNode; // 如果是右键点击,不打开抽屉 if (item == null || IsRightClickSelection) { return; } // 如果是目录,检查是否存在README.md string? mdFilePath = null; if (item.IsDirectory && !string.IsNullOrEmpty(item.FilePath)) { mdFilePath = FindMdFilePath(item.FilePath); } // 设置抽屉位置和大小 DrawerVm.DrawerPosition = DrawerPosition.Right; if (!string.IsNullOrEmpty(mdFilePath)) { DrawerVm.DrawerWidth = 450; } else { DrawerVm.DrawerWidth = 350; } // 统一的抽屉事件处理 DrawerVm.SetDrawerClosingAction(_ => { }); DrawerVm.setDrawerOpenedAction(() => { SelectNode = null; }); // 创建要在抽屉中显示的内容 var content = CreatePathingDetailContent(item, mdFilePath); // 打开抽屉 if (content != null) { DrawerVm.OpenDrawer(content); } } private string? FindMdFilePath(string dirPath) { string[] possibleMdFiles = { "README.md", "readme.md" }; foreach (var mdFile in possibleMdFiles) { string fullPath = Path.Combine(dirPath, mdFile); if (File.Exists(fullPath)) { return fullPath; } } return null; } private object? CreatePathingDetailContent(FileTreeNode node, string? mdFilePath = null) { // 创建显示路径任务详情的控件 var border = new Border { //Background = new SolidColorBrush(Color.FromRgb(0x2B, 0x2B, 0x2B)), Padding = new Thickness(20), CornerRadius = new CornerRadius(8), }; border.SetResourceReference(Border.BackgroundProperty, "ApplicationBackgroundBrush"); var mainGrid = new Grid(); mainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); // 标题行 mainGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); // 内容行,占满剩余空间 border.Child = mainGrid; // 添加标题 var titleTextBlock = new TextBlock { Text = node.FileName, FontSize = 20, FontWeight = FontWeights.Bold, Margin = new Thickness(0, 0, 0, 10), }; Grid.SetRow(titleTextBlock, 0); mainGrid.Children.Add(titleTextBlock); // 如果找到md文件,使用RichTextBox显示 if (!string.IsNullOrEmpty(mdFilePath)) { string markdown = File.ReadAllText(mdFilePath); var flowDoc = MarkdownToFlowDocumentConverter.ConvertToFlowDocument(markdown); var richTextBox = new RichTextBox { IsReadOnly = true, IsDocumentEnabled = true, BorderThickness = new Thickness(0), VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, Document = flowDoc, Background = Brushes.Transparent, VerticalAlignment = VerticalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(0, 10, 0, 0) }; Grid.SetRow(richTextBox, 1); mainGrid.Children.Add(richTextBox); } else if (!node.IsDirectory && !string.IsNullOrEmpty(node.FilePath)) { // 如果是文件而不是目录,显示更多详情 try { if (string.IsNullOrEmpty(node.Value?.Info.Description)) { return null; } var descriptionTextBlock = new TextBlock { Text = $"{node.Value?.Info.Description}", TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 10, 0, 0), VerticalAlignment = VerticalAlignment.Stretch }; Grid.SetRow(descriptionTextBlock, 1); mainGrid.Children.Add(descriptionTextBlock); } catch (Exception ex) { var errorTextBlock = new TextBlock { Text = $"读取文件信息时出错: {ex.Message}", TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 10, 0, 0), VerticalAlignment = VerticalAlignment.Stretch, Foreground = Brushes.Orange }; Grid.SetRow(errorTextBlock, 1); mainGrid.Children.Add(errorTextBlock); } } else { // 显示目录信息 - 使用StackPanel包装多个TextBlock var contentPanel = new StackPanel { Margin = new Thickness(0, 10, 0, 0), VerticalAlignment = VerticalAlignment.Top }; contentPanel.Children.Add(new TextBlock { Text = "这是一个目录,包含多个地图追踪任务。", TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 15) }); // 添加子项信息 if (node.Children.Count > 0) { contentPanel.Children.Add(new TextBlock { Text = $"包含 {node.Children.Count} 个子项", Margin = new Thickness(0, 0, 0, 5) }); } Grid.SetRow(contentPanel, 1); mainGrid.Children.Add(contentPanel); } return border; } }