Добавьте файлы проекта.
This commit is contained in:
397
SQLVision.UI/Controls/ControlFactory.cs
Normal file
397
SQLVision.UI/Controls/ControlFactory.cs
Normal file
@@ -0,0 +1,397 @@
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using SQLVision.Core.Enums;
|
||||
using SQLVision.Core.Interfaces;
|
||||
using SQLVision.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SQLVision.UI.Controls;
|
||||
|
||||
public class ControlFactory : IControlFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<ControlFactory> _logger;
|
||||
|
||||
public ControlFactory(IServiceProvider serviceProvider, ILogger<ControlFactory> logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public FrameworkElement CreateControl(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
try
|
||||
{
|
||||
var control = parameter.Type switch
|
||||
{
|
||||
ParameterType.String => CreateTextBox(parameter, onValueChanged),
|
||||
ParameterType.Int => CreateNumberBox(parameter, onValueChanged),
|
||||
ParameterType.Decimal => CreateNumberBox(parameter, onValueChanged, true),
|
||||
ParameterType.DateTime => CreateDatePicker(parameter, onValueChanged),
|
||||
ParameterType.Bool => CreateCheckBox(parameter, onValueChanged),
|
||||
ParameterType.Table => CreateComboBox(parameter, onValueChanged),
|
||||
ParameterType.MultiSelect => CreateListBox(parameter, onValueChanged),
|
||||
ParameterType.Color => CreateColorPicker(parameter, onValueChanged),
|
||||
ParameterType.File => CreateFilePicker(parameter, onValueChanged),
|
||||
ParameterType.Json => CreateJsonEditor(parameter, onValueChanged),
|
||||
_ => CreateTextBox(parameter, onValueChanged)
|
||||
};
|
||||
|
||||
// Настройка общих свойств
|
||||
control.Tag = parameter;
|
||||
control.IsEnabled = !parameter.IsRequired || parameter.DefaultValue == null;
|
||||
|
||||
// Добавление подсказки
|
||||
if (!string.IsNullOrEmpty(parameter.Description))
|
||||
{
|
||||
ToolTipService.SetToolTip(control, parameter.Description);
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating control for parameter {Parameter}", parameter.Name);
|
||||
return CreateFallbackControl(parameter, onValueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private FrameworkElement CreateTextBox(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
PlaceholderText = parameter.Watermark ?? $"Введите {parameter.DisplayName.ToLower()}",
|
||||
Text = parameter.DefaultValue?.ToString() ?? string.Empty
|
||||
};
|
||||
|
||||
textBox.TextChanged += (s, e) => onValueChanged(textBox.Text);
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateNumberBox(ScriptParameter parameter, Action<object> onValueChanged, bool isDecimal = false)
|
||||
{
|
||||
var numberBox = new NumberBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
PlaceholderText = parameter.Watermark ?? $"Введите {parameter.DisplayName.ToLower()}",
|
||||
SmallChange = isDecimal ? 0.1 : 1,
|
||||
LargeChange = isDecimal ? 1 : 10,
|
||||
SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Inline,
|
||||
AcceptsExpression = false
|
||||
};
|
||||
|
||||
if (isDecimal)
|
||||
{
|
||||
numberBox.Value = Convert.ToDouble(parameter.DefaultValue ?? 0);
|
||||
numberBox.ValueChanged += (s, e) => onValueChanged(e.NewValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
numberBox.Value = Convert.ToInt32(parameter.DefaultValue ?? 0);
|
||||
numberBox.ValueChanged += (s, e) => onValueChanged((int)e.NewValue);
|
||||
}
|
||||
|
||||
// Применение правил валидации
|
||||
if (parameter.ValidationRules != null)
|
||||
{
|
||||
if (parameter.ValidationRules.TryGetValue("min", out var min))
|
||||
numberBox.Minimum = Convert.ToDouble(min);
|
||||
|
||||
if (parameter.ValidationRules.TryGetValue("max", out var max))
|
||||
numberBox.Maximum = Convert.ToDouble(max);
|
||||
}
|
||||
|
||||
return numberBox;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateDatePicker(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var datePicker = new DatePicker
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
Date = parameter.DefaultValue is DateTime defaultDate ?
|
||||
DateTimeOffset.Parse(defaultDate.ToString()) : DateTimeOffset.Now
|
||||
};
|
||||
|
||||
datePicker.DateChanged += (s, e) => onValueChanged(e.NewDate.DateTime);
|
||||
|
||||
return datePicker;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateCheckBox(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = parameter.DisplayName,
|
||||
IsChecked = parameter.DefaultValue is bool defaultBool ? defaultBool : false
|
||||
};
|
||||
|
||||
checkBox.Checked += (s, e) => onValueChanged(true);
|
||||
checkBox.Unchecked += (s, e) => onValueChanged(false);
|
||||
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateComboBox(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var comboBox = new ComboBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
PlaceholderText = parameter.Watermark ?? $"Выберите {parameter.DisplayName.ToLower()}",
|
||||
DisplayMemberPath = parameter.DisplayMember,
|
||||
SelectedValuePath = parameter.ValueMember
|
||||
};
|
||||
|
||||
// Загрузка данных асинхронно
|
||||
LoadComboBoxDataAsync(comboBox, parameter).ConfigureAwait(false);
|
||||
|
||||
comboBox.SelectionChanged += (s, e) =>
|
||||
{
|
||||
if (comboBox.SelectedValue != null)
|
||||
onValueChanged(comboBox.SelectedValue);
|
||||
};
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private async Task LoadComboBoxDataAsync(ComboBox comboBox, ScriptParameter parameter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameter.TableQuery)) return;
|
||||
|
||||
try
|
||||
{
|
||||
// Используем сервис выполнения SQL для загрузки данных
|
||||
var executionService = _serviceProvider.GetService<ISqlExecutionService>();
|
||||
var result = await executionService.ExecuteAsync(
|
||||
parameter.TableQuery,
|
||||
new Dictionary<string, object>(),
|
||||
GetConnectionString());
|
||||
|
||||
if (result.IsSuccess && result.Data.Tables.Count > 0)
|
||||
{
|
||||
comboBox.ItemsSource = result.Data.Tables[0].DefaultView;
|
||||
|
||||
// Установка значения по умолчанию
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
comboBox.SelectedValue = parameter.DefaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading combo box data for {Parameter}", parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private FrameworkElement CreateListBox(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var listBox = new ListBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
SelectionMode = ListViewSelectionMode.Multiple
|
||||
};
|
||||
|
||||
// Загрузка данных для ListBox
|
||||
// Аналогично ComboBox
|
||||
|
||||
listBox.SelectionChanged += (s, e) =>
|
||||
{
|
||||
var selectedValues = listBox.SelectedItems.Cast<DataRowView>()
|
||||
.Select(r => r[parameter.ValueMember])
|
||||
.ToList();
|
||||
|
||||
onValueChanged(selectedValues);
|
||||
};
|
||||
|
||||
return listBox;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateColorPicker(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var colorPicker = new ColorPicker
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
ColorSpectrumShape = ColorSpectrumShape.Box,
|
||||
IsMoreButtonVisible = true,
|
||||
IsColorSliderVisible = true,
|
||||
IsColorChannelTextInputVisible = true,
|
||||
IsHexInputVisible = true
|
||||
};
|
||||
|
||||
if (parameter.DefaultValue is string defaultColor)
|
||||
{
|
||||
if (ColorHelper.TryParse(defaultColor, out var color))
|
||||
{
|
||||
colorPicker.Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
colorPicker.ColorChanged += (s, e) =>
|
||||
onValueChanged($"#{e.NewColor.R:X2}{e.NewColor.G:X2}{e.NewColor.B:X2}");
|
||||
|
||||
return colorPicker;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateFilePicker(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var stackPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
|
||||
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
PlaceholderText = "Путь к файлу",
|
||||
Width = 200,
|
||||
IsReadOnly = true
|
||||
};
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Content = "Выбрать",
|
||||
VerticalAlignment = VerticalAlignment.Bottom
|
||||
};
|
||||
|
||||
button.Click += async (s, e) =>
|
||||
{
|
||||
var openPicker = new FileOpenPicker();
|
||||
openPicker.ViewMode = PickerViewMode.List;
|
||||
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
|
||||
|
||||
if (parameter.ValidationRules != null &&
|
||||
parameter.ValidationRules.TryGetValue("extensions", out var extensions))
|
||||
{
|
||||
foreach (var ext in extensions.ToString().Split(','))
|
||||
{
|
||||
openPicker.FileTypeFilter.Add(ext.Trim());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
openPicker.FileTypeFilter.Add("*");
|
||||
}
|
||||
|
||||
var file = await openPicker.PickSingleFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
textBox.Text = file.Path;
|
||||
onValueChanged(file.Path);
|
||||
}
|
||||
};
|
||||
|
||||
stackPanel.Children.Add(textBox);
|
||||
stackPanel.Children.Add(button);
|
||||
|
||||
return stackPanel;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateJsonEditor(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
PlaceholderText = "Введите JSON",
|
||||
AcceptsReturn = true,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Height = 100,
|
||||
FontFamily = new FontFamily("Consolas")
|
||||
};
|
||||
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
textBox.Text = JsonSerializer.Serialize(parameter.DefaultValue,
|
||||
new JsonSerializerOptions { WriteIndented = true });
|
||||
}
|
||||
|
||||
textBox.TextChanged += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Deserialize<JsonElement>(textBox.Text);
|
||||
onValueChanged(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем ошибки парсинга JSON
|
||||
}
|
||||
};
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private FrameworkElement CreateFallbackControl(ScriptParameter parameter, Action<object> onValueChanged)
|
||||
{
|
||||
return new TextBox
|
||||
{
|
||||
Header = parameter.DisplayName,
|
||||
Text = $"Ошибка создания контрола для типа {parameter.Type}",
|
||||
IsReadOnly = true,
|
||||
Foreground = new SolidColorBrush(Colors.Red)
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateControlState(FrameworkElement control, ScriptParameter parameter, Dictionary<string, object> currentValues)
|
||||
{
|
||||
// Обновление состояния контрола на основе зависимостей
|
||||
if (!string.IsNullOrEmpty(parameter.DependsOn))
|
||||
{
|
||||
var isEnabled = CheckDependency(parameter, currentValues);
|
||||
control.IsEnabled = isEnabled;
|
||||
|
||||
if (!isEnabled)
|
||||
{
|
||||
// Сброс значения, если контрол отключен
|
||||
ResetControlValue(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDependency(ScriptParameter parameter, Dictionary<string, object> currentValues)
|
||||
{
|
||||
if (!currentValues.TryGetValue(parameter.DependsOn, out var dependencyValue))
|
||||
return false;
|
||||
|
||||
if (parameter.DependencyValues != null)
|
||||
{
|
||||
return parameter.DependencyValues
|
||||
.Any(kvp => object.Equals(kvp.Value, dependencyValue));
|
||||
}
|
||||
|
||||
return dependencyValue != null && !string.IsNullOrWhiteSpace(dependencyValue.ToString());
|
||||
}
|
||||
|
||||
private void ResetControlValue(FrameworkElement control)
|
||||
{
|
||||
switch (control)
|
||||
{
|
||||
case TextBox textBox:
|
||||
textBox.Text = string.Empty;
|
||||
break;
|
||||
case ComboBox comboBox:
|
||||
comboBox.SelectedIndex = -1;
|
||||
break;
|
||||
case CheckBox checkBox:
|
||||
checkBox.IsChecked = false;
|
||||
break;
|
||||
case DatePicker datePicker:
|
||||
datePicker.Date = DateTimeOffset.Now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetConnectionString()
|
||||
{
|
||||
// Получение строки подключения из конфигурации
|
||||
var configuration = _serviceProvider.GetService<IConfiguration>();
|
||||
return configuration.GetConnectionString("Default") ??
|
||||
configuration["Database:DefaultConnection"];
|
||||
}
|
||||
}
|
||||
33
SQLVision.UI/SQLVision.UI.csproj
Normal file
33
SQLVision.UI/SQLVision.UI.csproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>SQLVision.UI</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Все зависимости -->
|
||||
<ProjectReference Include="..\SQLVision.Services\SQLVision.Services.csproj" />
|
||||
<ProjectReference Include="..\SQLVision.Visualizers\SQLVision.Visualizers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.230913002" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
528
SQLVision.UI/ViewModels/MainViewModel.cs
Normal file
528
SQLVision.UI/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,528 @@
|
||||
using SQLVision.Core.Enums;
|
||||
using SQLVision.Core.Interfaces;
|
||||
using SQLVision.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace SQLVision.UI.ViewModels;
|
||||
|
||||
public class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly IScriptManager _scriptManager;
|
||||
private readonly ISqlExecutionService _executionService;
|
||||
private readonly IExportService _exportService;
|
||||
private readonly IControlFactory _controlFactory;
|
||||
private readonly IVisualizerFactory _visualizerFactory;
|
||||
private readonly ILogger<MainViewModel> _logger;
|
||||
|
||||
private readonly ObservableCollection<ScriptMetadata> _scripts = new();
|
||||
private readonly ObservableCollection<ScriptCategory> _scriptCategories = new();
|
||||
private readonly ObservableCollection<ExecutionHistoryItem> _history = new();
|
||||
private readonly ObservableCollection<ResultTabViewModel> _resultTabs = new();
|
||||
|
||||
private ScriptMetadata _selectedScript;
|
||||
private bool _isBusy;
|
||||
private string _statusMessage;
|
||||
private ResultTabViewModel _selectedResultTab;
|
||||
private string _searchText;
|
||||
|
||||
public ObservableCollection<ScriptCategory> ScriptCategories => _scriptCategories;
|
||||
public ObservableCollection<ExecutionHistoryItem> History => _history;
|
||||
public ObservableCollection<ResultTabViewModel> ResultTabs => _resultTabs;
|
||||
|
||||
public ScriptMetadata SelectedScript
|
||||
{
|
||||
get => _selectedScript;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedScript, value))
|
||||
{
|
||||
OnSelectedScriptChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
|
||||
public ResultTabViewModel SelectedResultTab
|
||||
{
|
||||
get => _selectedResultTab;
|
||||
set => SetProperty(ref _selectedResultTab, value);
|
||||
}
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _searchText, value))
|
||||
{
|
||||
FilterScripts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ParameterViewModel> Parameters { get; } = new();
|
||||
|
||||
public IAsyncRelayCommand LoadScriptsCommand { get; }
|
||||
public IAsyncRelayCommand ExecuteCommand { get; }
|
||||
public IAsyncRelayCommand ExportCommand { get; }
|
||||
public IRelayCommand CopySqlCommand { get; }
|
||||
public IRelayCommand ClearResultsCommand { get; }
|
||||
public IRelayCommand SaveParametersCommand { get; }
|
||||
public IRelayCommand LoadParametersCommand { get; }
|
||||
|
||||
public MainViewModel(
|
||||
IScriptManager scriptManager,
|
||||
ISqlExecutionService executionService,
|
||||
IExportService exportService,
|
||||
IControlFactory controlFactory,
|
||||
IVisualizerFactory visualizerFactory,
|
||||
ILogger<MainViewModel> logger)
|
||||
{
|
||||
_scriptManager = scriptManager;
|
||||
_executionService = executionService;
|
||||
_exportService = exportService;
|
||||
_controlFactory = controlFactory;
|
||||
_visualizerFactory = visualizerFactory;
|
||||
_logger = logger;
|
||||
|
||||
LoadScriptsCommand = new AsyncRelayCommand(LoadScriptsAsync);
|
||||
ExecuteCommand = new AsyncRelayCommand(ExecuteScriptAsync, CanExecuteScript);
|
||||
ExportCommand = new AsyncRelayCommand(ExportResultsAsync, CanExportResults);
|
||||
CopySqlCommand = new RelayCommand(CopySqlToClipboard);
|
||||
ClearResultsCommand = new RelayCommand(ClearResults);
|
||||
SaveParametersCommand = new RelayCommand(SaveParameters);
|
||||
LoadParametersCommand = new RelayCommand(LoadParameters);
|
||||
|
||||
// Подписка на события
|
||||
_scriptManager.ScriptChanged += OnScriptChanged;
|
||||
_scriptManager.ScriptsReloaded += OnScriptsReloaded;
|
||||
}
|
||||
|
||||
private async Task LoadScriptsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "Загрузка скриптов...";
|
||||
|
||||
var scripts = await _scriptManager.LoadScriptsAsync();
|
||||
_scripts.Clear();
|
||||
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
_scripts.Add(script);
|
||||
}
|
||||
|
||||
CategorizeScripts();
|
||||
StatusMessage = $"Загружено {_scripts.Count} скриптов";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading scripts");
|
||||
StatusMessage = $"Ошибка загрузки: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CategorizeScripts()
|
||||
{
|
||||
_scriptCategories.Clear();
|
||||
|
||||
var categories = _scripts
|
||||
.GroupBy(s => s.Category ?? "Без категории")
|
||||
.OrderBy(g => g.Key);
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var scriptCategory = new ScriptCategory
|
||||
{
|
||||
Name = category.Key,
|
||||
Scripts = new ObservableCollection<ScriptMetadata>(category.OrderBy(s => s.FileName))
|
||||
};
|
||||
|
||||
_scriptCategories.Add(scriptCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterScripts()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
// Показать все скрипты
|
||||
foreach (var category in _scriptCategories)
|
||||
{
|
||||
foreach (var script in category.Scripts)
|
||||
{
|
||||
script.IsVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchLower = SearchText.ToLower();
|
||||
|
||||
foreach (var category in _scriptCategories)
|
||||
{
|
||||
foreach (var script in category.Scripts)
|
||||
{
|
||||
script.IsVisible =
|
||||
script.FileName.ToLower().Contains(searchLower) ||
|
||||
script.Description?.ToLower().Contains(searchLower) == true ||
|
||||
script.Tags.Any(t => t.ToLower().Contains(searchLower));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectedScriptChanged()
|
||||
{
|
||||
Parameters.Clear();
|
||||
|
||||
if (_selectedScript == null) return;
|
||||
|
||||
// Создание ViewModel для каждого параметра
|
||||
foreach (var param in _selectedScript.Parameters.OrderBy(p => p.Order))
|
||||
{
|
||||
var paramVm = new ParameterViewModel(param, _controlFactory);
|
||||
paramVm.ValueChanged += OnParameterValueChanged;
|
||||
Parameters.Add(paramVm);
|
||||
}
|
||||
|
||||
// Восстановление сохраненных значений
|
||||
LoadSavedParameters();
|
||||
}
|
||||
|
||||
private void OnParameterValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
ExecuteCommand.NotifyCanExecuteChanged();
|
||||
|
||||
// Обновление зависимых параметров
|
||||
if (sender is ParameterViewModel changedParam)
|
||||
{
|
||||
UpdateDependentParameters(changedParam);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDependentParameters(ParameterViewModel changedParam)
|
||||
{
|
||||
foreach (var paramVm in Parameters)
|
||||
{
|
||||
if (paramVm.Parameter.DependsOn == changedParam.Parameter.Name)
|
||||
{
|
||||
paramVm.UpdateDependencies(GetCurrentParameterValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetCurrentParameterValues()
|
||||
{
|
||||
return Parameters.ToDictionary(
|
||||
p => p.Parameter.Name,
|
||||
p => p.Value ?? p.Parameter.DefaultValue);
|
||||
}
|
||||
|
||||
private bool CanExecuteScript()
|
||||
{
|
||||
if (_selectedScript == null) return false;
|
||||
|
||||
// Проверка обязательных параметров
|
||||
foreach (var paramVm in Parameters)
|
||||
{
|
||||
if (paramVm.Parameter.IsRequired &&
|
||||
(paramVm.Value == null || string.IsNullOrWhiteSpace(paramVm.Value.ToString())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ExecuteScriptAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "Выполнение скрипта...";
|
||||
|
||||
var parameters = GetCurrentParameterValues();
|
||||
|
||||
var result = await _executionService.ExecuteAsync(_selectedScript, parameters);
|
||||
|
||||
// Добавление в историю
|
||||
var historyItem = new ExecutionHistoryItem
|
||||
{
|
||||
ScriptId = _selectedScript.Id,
|
||||
ScriptName = _selectedScript.FileName,
|
||||
ExecutionTime = DateTime.Now,
|
||||
Duration = result.ExecutionTime,
|
||||
Success = result.IsSuccess,
|
||||
Parameters = new Dictionary<string, object>(parameters),
|
||||
RowCount = result.RowCount,
|
||||
ErrorMessage = result.ErrorMessage,
|
||||
ExecutedSql = result.ExecutedSql
|
||||
};
|
||||
|
||||
_history.Insert(0, historyItem);
|
||||
|
||||
// Очистка старых записей истории
|
||||
while (_history.Count > 1000)
|
||||
{
|
||||
_history.RemoveAt(_history.Count - 1);
|
||||
}
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
CreateResultTabs(result);
|
||||
StatusMessage = $"Выполнено за {result.ExecutionTime.TotalSeconds:F2} сек. Получено строк: {result.RowCount}";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = $"Ошибка: {result.ErrorMessage}";
|
||||
ShowErrorDialog(result.ErrorMessage);
|
||||
}
|
||||
|
||||
// Сохранение параметров
|
||||
SaveParameters();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error executing script");
|
||||
StatusMessage = $"Ошибка: {ex.Message}";
|
||||
ShowErrorDialog(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateResultTabs(ExecutionResult result)
|
||||
{
|
||||
// Удаляем старые вкладки
|
||||
ResultTabs.Clear();
|
||||
|
||||
for (int i = 0; i < result.Data.Tables.Count; i++)
|
||||
{
|
||||
var table = result.Data.Tables[i];
|
||||
var outputDef = i < _selectedScript.Outputs.Count
|
||||
? _selectedScript.Outputs[i]
|
||||
: CreateDefaultOutputDefinition(table, i);
|
||||
|
||||
var visualizer = _visualizerFactory.GetVisualizer(outputDef.Type);
|
||||
var content = visualizer.Visualize(table, outputDef);
|
||||
|
||||
var tabVm = new ResultTabViewModel
|
||||
{
|
||||
Title = outputDef.Description,
|
||||
Content = content,
|
||||
DataTable = table,
|
||||
OutputDefinition = outputDef,
|
||||
CanExport = true,
|
||||
CanCopy = true
|
||||
};
|
||||
|
||||
ResultTabs.Add(tabVm);
|
||||
}
|
||||
|
||||
if (ResultTabs.Any())
|
||||
{
|
||||
SelectedResultTab = ResultTabs[0];
|
||||
}
|
||||
}
|
||||
|
||||
private OutputDefinition CreateDefaultOutputDefinition(DataTable table, int index)
|
||||
{
|
||||
return new OutputDefinition
|
||||
{
|
||||
Type = OutputType.Table,
|
||||
Description = $"Результат {index + 1}",
|
||||
DataTableName = table.TableName
|
||||
};
|
||||
}
|
||||
|
||||
private bool CanExportResults()
|
||||
{
|
||||
return SelectedResultTab != null &&
|
||||
SelectedResultTab.DataTable != null &&
|
||||
SelectedResultTab.DataTable.Rows.Count > 0;
|
||||
}
|
||||
|
||||
private async Task ExportResultsAsync()
|
||||
{
|
||||
if (SelectedResultTab?.DataTable == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var savePicker = new FileSavePicker();
|
||||
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
|
||||
savePicker.FileTypeChoices.Add("Excel файл", new List<string> { ".xlsx" });
|
||||
savePicker.FileTypeChoices.Add("CSV файл", new List<string> { ".csv" });
|
||||
savePicker.FileTypeChoices.Add("JSON файл", new List<string> { ".json" });
|
||||
savePicker.SuggestedFileName = $"{_selectedScript.FileName}_{DateTime.Now:yyyyMMdd_HHmmss}";
|
||||
|
||||
var file = await savePicker.PickSaveFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
var options = new ExportOptions
|
||||
{
|
||||
Format = Path.GetExtension(file.Path).TrimStart('.').ToUpper(),
|
||||
IncludeHeaders = true,
|
||||
AutoFilter = true
|
||||
};
|
||||
|
||||
await _exportService.ExportAsync(SelectedResultTab.DataTable, file.Path, options);
|
||||
|
||||
StatusMessage = $"Экспортировано в {file.Path}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error exporting results");
|
||||
StatusMessage = $"Ошибка экспорта: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void CopySqlToClipboard()
|
||||
{
|
||||
if (_selectedScript == null) return;
|
||||
|
||||
var parameters = GetCurrentParameterValues();
|
||||
var sql = FormatSqlWithParameters(_selectedScript.ProcessedSql, parameters);
|
||||
|
||||
var package = new DataPackage();
|
||||
package.SetText(sql);
|
||||
Clipboard.SetContent(package);
|
||||
|
||||
StatusMessage = "SQL скопирован в буфер обмена";
|
||||
}
|
||||
|
||||
private string FormatSqlWithParameters(string sql, Dictionary<string, object> parameters)
|
||||
{
|
||||
var result = new StringBuilder(sql);
|
||||
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var paramName = $"@{param.Key}";
|
||||
var paramValue = FormatParameterForDisplay(param.Value);
|
||||
|
||||
result = result.Replace(paramName, paramValue);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private string FormatParameterForDisplay(object value)
|
||||
{
|
||||
if (value == null) return "NULL";
|
||||
|
||||
return value switch
|
||||
{
|
||||
string str => $"N'{str.Replace("'", "''")}'",
|
||||
DateTime dt => $"'{dt:yyyy-MM-dd HH:mm:ss}'",
|
||||
bool b => b ? "1" : "0",
|
||||
_ => value.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private void ClearResults()
|
||||
{
|
||||
ResultTabs.Clear();
|
||||
StatusMessage = "Результаты очищены";
|
||||
}
|
||||
|
||||
private void SaveParameters()
|
||||
{
|
||||
if (_selectedScript == null) return;
|
||||
|
||||
var parameters = GetCurrentParameterValues();
|
||||
var settings = ApplicationData.Current.LocalSettings;
|
||||
|
||||
var dict = new Dictionary<string, object>();
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
dict[param.Key] = param.Value;
|
||||
}
|
||||
|
||||
var json = JsonSerializer.Serialize(dict);
|
||||
settings.Values[$"ScriptParams_{_selectedScript.Id}"] = json;
|
||||
|
||||
StatusMessage = "Параметры сохранены";
|
||||
}
|
||||
|
||||
private void LoadParameters()
|
||||
{
|
||||
if (_selectedScript == null) return;
|
||||
|
||||
var settings = ApplicationData.Current.LocalSettings;
|
||||
if (settings.Values.TryGetValue($"ScriptParams_{_selectedScript.Id}", out var jsonObj))
|
||||
{
|
||||
try
|
||||
{
|
||||
var savedParams = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonObj.ToString());
|
||||
|
||||
foreach (var paramVm in Parameters)
|
||||
{
|
||||
if (savedParams.TryGetValue(paramVm.Parameter.Name, out var value))
|
||||
{
|
||||
paramVm.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
StatusMessage = "Параметры загружены";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error loading saved parameters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowErrorDialog(string message)
|
||||
{
|
||||
// Реализация диалога ошибки
|
||||
// Можно использовать ContentDialog или другое UI решение
|
||||
}
|
||||
|
||||
private void OnScriptChanged(object sender, ScriptChangedEventArgs e)
|
||||
{
|
||||
// Обновление UI при изменении скрипта
|
||||
DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
|
||||
{
|
||||
CategorizeScripts();
|
||||
|
||||
if (e.ChangeType == ScriptChangeType.Deleted &&
|
||||
_selectedScript?.FullPath == e.FilePath)
|
||||
{
|
||||
SelectedScript = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnScriptsReloaded(object sender, ScriptsReloadedEventArgs e)
|
||||
{
|
||||
DispatcherQueue.GetForCurrentThread().TryEnqueue(CategorizeScripts);
|
||||
}
|
||||
}
|
||||
124
SQLVision.UI/ViewModels/ParameterViewModel.cs
Normal file
124
SQLVision.UI/ViewModels/ParameterViewModel.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using SQLVision.Core.Enums;
|
||||
using SQLVision.Core.Interfaces;
|
||||
using SQLVision.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SQLVision.UI.ViewModels;
|
||||
|
||||
public class ParameterViewModel : ObservableObject
|
||||
{
|
||||
private readonly IControlFactory _controlFactory;
|
||||
private FrameworkElement _control;
|
||||
private object _value;
|
||||
private bool _isEnabled = true;
|
||||
private string _validationError;
|
||||
|
||||
public ScriptParameter Parameter { get; }
|
||||
public string Name => Parameter.Name;
|
||||
public string DisplayName => Parameter.DisplayName ?? Parameter.Name;
|
||||
public string Description => Parameter.Description;
|
||||
public ParameterType Type => Parameter.Type;
|
||||
|
||||
public FrameworkElement Control
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_control == null)
|
||||
{
|
||||
_control = _controlFactory.CreateControl(Parameter, OnValueChanged);
|
||||
}
|
||||
return _control;
|
||||
}
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get => _value ?? Parameter.DefaultValue;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _value, value))
|
||||
{
|
||||
ValidateValue();
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set => SetProperty(ref _isEnabled, value);
|
||||
}
|
||||
|
||||
public string ValidationError
|
||||
{
|
||||
get => _validationError;
|
||||
private set => SetProperty(ref _validationError, value);
|
||||
}
|
||||
|
||||
public bool HasError => !string.IsNullOrEmpty(ValidationError);
|
||||
|
||||
public event EventHandler ValueChanged;
|
||||
|
||||
public ParameterViewModel(ScriptParameter parameter, IControlFactory controlFactory)
|
||||
{
|
||||
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
|
||||
_controlFactory = controlFactory ?? throw new ArgumentNullException(nameof(controlFactory));
|
||||
|
||||
_value = parameter.DefaultValue;
|
||||
}
|
||||
|
||||
private void OnValueChanged(object value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
private void ValidateValue()
|
||||
{
|
||||
if (Parameter.IsValid(Value))
|
||||
{
|
||||
ValidationError = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ValidationError = "Неверное значение параметра";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDependencies(Dictionary<string, object> currentValues)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Parameter.DependsOn))
|
||||
{
|
||||
IsEnabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentValues.TryGetValue(Parameter.DependsOn, out var dependencyValue))
|
||||
{
|
||||
IsEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка условий зависимости
|
||||
if (Parameter.DependencyValues != null)
|
||||
{
|
||||
var isEnabled = Parameter.DependencyValues
|
||||
.Any(kvp => object.Equals(kvp.Value, dependencyValue));
|
||||
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsEnabled = dependencyValue != null &&
|
||||
!string.IsNullOrWhiteSpace(dependencyValue.ToString());
|
||||
}
|
||||
|
||||
if (!IsEnabled)
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
SQLVision.UI/ViewModels/ResultTabViewModel.cs
Normal file
70
SQLVision.UI/ViewModels/ResultTabViewModel.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using SQLVision.Core.Models;
|
||||
using System.Data;
|
||||
|
||||
namespace SQLVision.UI.ViewModels;
|
||||
|
||||
public class ResultTabViewModel : ObservableObject
|
||||
{
|
||||
private string _title;
|
||||
private FrameworkElement _content;
|
||||
private DataTable _dataTable;
|
||||
private OutputDefinition _outputDefinition;
|
||||
private bool _canExport;
|
||||
private bool _canCopy;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
public FrameworkElement Content
|
||||
{
|
||||
get => _content;
|
||||
set => SetProperty(ref _content, value);
|
||||
}
|
||||
|
||||
public DataTable DataTable
|
||||
{
|
||||
get => _dataTable;
|
||||
set => SetProperty(ref _dataTable, value);
|
||||
}
|
||||
|
||||
public OutputDefinition OutputDefinition
|
||||
{
|
||||
get => _outputDefinition;
|
||||
set => SetProperty(ref _outputDefinition, value);
|
||||
}
|
||||
|
||||
public bool CanExport
|
||||
{
|
||||
get => _canExport;
|
||||
set => SetProperty(ref _canExport, value);
|
||||
}
|
||||
|
||||
public bool CanCopy
|
||||
{
|
||||
get => _canCopy;
|
||||
set => SetProperty(ref _canCopy, value);
|
||||
}
|
||||
|
||||
public IRelayCommand ExportCommand { get; }
|
||||
public IRelayCommand CopyDataCommand { get; }
|
||||
|
||||
public ResultTabViewModel()
|
||||
{
|
||||
ExportCommand = new RelayCommand(Export);
|
||||
CopyDataCommand = new RelayCommand(CopyData);
|
||||
}
|
||||
|
||||
private void Export()
|
||||
{
|
||||
// Экспорт данных вкладки
|
||||
}
|
||||
|
||||
private void CopyData()
|
||||
{
|
||||
// Копирование данных в буфер обмена
|
||||
}
|
||||
}
|
||||
29
SQLVision.UI/ViewModels/ScriptCategory.cs
Normal file
29
SQLVision.UI/ViewModels/ScriptCategory.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using SQLVision.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace SQLVision.UI.ViewModels;
|
||||
|
||||
public class ScriptCategory : ObservableObject
|
||||
{
|
||||
private string _name;
|
||||
private ObservableCollection<ScriptMetadata> _scripts;
|
||||
private bool _isExpanded = true;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<ScriptMetadata> Scripts
|
||||
{
|
||||
get => _scripts;
|
||||
set => SetProperty(ref _scripts, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
}
|
||||
359
SQLVision.UI/Views/MainWindow.xaml
Normal file
359
SQLVision.UI/Views/MainWindow.xaml
Normal file
@@ -0,0 +1,359 @@
|
||||
<Window
|
||||
x:Class="SQLVision.UI.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:SQLVision.UI.Controls"
|
||||
xmlns:charts="using:LiveChartsCore.SkiaSharpView.WinUI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:converters="using:SQLVision.UI.Converters"
|
||||
mc:Ignorable="d"
|
||||
Title="SQLVision"
|
||||
Width="1200"
|
||||
Height="800"
|
||||
MinWidth="800"
|
||||
MinHeight="600">
|
||||
|
||||
<Window.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
|
||||
<converters:InverseBooleanConverter x:Key="InverseBool"/>
|
||||
|
||||
<Style TargetType="Button" x:Key="IconButtonStyle">
|
||||
<Setter Property="Margin" Value="2"/>
|
||||
<Setter Property="Padding" Value="8,4"/>
|
||||
<Setter Property="MinWidth" Value="80"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Панель инструментов -->
|
||||
<CommandBar Grid.Row="0" DefaultLabelPosition="Right">
|
||||
<AppBarButton
|
||||
Icon="Refresh"
|
||||
Label="Обновить скрипты"
|
||||
Command="{x:Bind ViewModel.LoadScriptsCommand}"/>
|
||||
<AppBarSeparator/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="Play"
|
||||
Label="Выполнить"
|
||||
Command="{x:Bind ViewModel.ExecuteCommand}"
|
||||
IsEnabled="{x:Bind ViewModel.ExecuteCommand.IsRunning, Converter={StaticResource InverseBool}, Mode=OneWay}"/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="Save"
|
||||
Label="Экспорт"
|
||||
Command="{x:Bind ViewModel.ExportCommand}"/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="Copy"
|
||||
Label="Копировать SQL"
|
||||
Command="{x:Bind ViewModel.CopySqlCommand}"/>
|
||||
|
||||
<AppBarSeparator/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="Clear"
|
||||
Label="Очистить результаты"
|
||||
Command="{x:Bind ViewModel.ClearResultsCommand}"/>
|
||||
|
||||
<AppBarSeparator/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="SaveLocal"
|
||||
Label="Сохранить параметры"
|
||||
Command="{x:Bind ViewModel.SaveParametersCommand}"/>
|
||||
|
||||
<AppBarButton
|
||||
Icon="OpenLocal"
|
||||
Label="Загрузить параметры"
|
||||
Command="{x:Bind ViewModel.LoadParametersCommand}"/>
|
||||
|
||||
<CommandBar.Content>
|
||||
<TextBlock
|
||||
Text="SQLVision"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Margin="12,0"/>
|
||||
</CommandBar.Content>
|
||||
</CommandBar>
|
||||
|
||||
<!-- Основное содержимое -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" MinWidth="250"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Левая панель: скрипты и параметры -->
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Поиск -->
|
||||
<AutoSuggestBox
|
||||
Grid.Row="0"
|
||||
PlaceholderText="Поиск скриптов..."
|
||||
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="8">
|
||||
<AutoSuggestBox.QueryIcon>
|
||||
<SymbolIcon Symbol="Find"/>
|
||||
</AutoSuggestBox.QueryIcon>
|
||||
</AutoSuggestBox>
|
||||
|
||||
<!-- Дерево скриптов -->
|
||||
<TreeView
|
||||
Grid.Row="1"
|
||||
ItemsSource="{x:Bind ViewModel.ScriptCategories, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedScript, Mode=TwoWay}"
|
||||
Margin="8">
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ScriptCategory">
|
||||
<TreeViewItem
|
||||
ItemsSource="{x:Bind Scripts}"
|
||||
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}">
|
||||
|
||||
<TreeViewItem.Header>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{x:Bind Name}" FontWeight="SemiBold"/>
|
||||
<TextBlock
|
||||
Text="{x:Bind Scripts.Count}"
|
||||
Foreground="Gray"
|
||||
FontSize="12"/>
|
||||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
|
||||
<TreeViewItem.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:ScriptMetadata">
|
||||
<TreeViewItem>
|
||||
<StackPanel Orientation="Vertical" Spacing="2">
|
||||
<TextBlock
|
||||
Text="{x:Bind FileName}"
|
||||
TextWrapping="Wrap"
|
||||
FontWeight="Normal"/>
|
||||
<TextBlock
|
||||
Text="{x:Bind Description}"
|
||||
FontSize="11"
|
||||
Foreground="Gray"
|
||||
TextWrapping="Wrap"
|
||||
MaxLines="2"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
</TreeViewItem>
|
||||
</DataTemplate>
|
||||
</TreeViewItem.ItemTemplate>
|
||||
</TreeViewItem>
|
||||
</DataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- Параметры скрипта -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="8"
|
||||
MaxHeight="400">
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel
|
||||
x:Name="ParametersPanel"
|
||||
Spacing="12"
|
||||
Visibility="{x:Bind ViewModel.SelectedScript, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}">
|
||||
|
||||
<TextBlock
|
||||
Text="Параметры"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Parameters, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ParameterViewModel">
|
||||
<StackPanel Spacing="4" Margin="0,0,0,8">
|
||||
<ContentPresenter Content="{x:Bind Control}"/>
|
||||
|
||||
<TextBlock
|
||||
Text="{x:Bind ValidationError}"
|
||||
Foreground="Red"
|
||||
FontSize="11"
|
||||
Visibility="{x:Bind HasError, Converter={StaticResource BoolToVisibility}}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter
|
||||
Grid.Column="1"
|
||||
Width="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="Transparent"/>
|
||||
|
||||
<!-- Правая панель: результаты -->
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Вкладки с результатами -->
|
||||
<muxc:TabView
|
||||
Grid.Row="0"
|
||||
ItemsSource="{x:Bind ViewModel.ResultTabs, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedResultTab, Mode=TwoWay}"
|
||||
TabWidthMode="SizeToContent"
|
||||
CanReorderTabs="True"
|
||||
CanCloseTabs="True"
|
||||
TabCloseRequested="OnTabCloseRequested">
|
||||
|
||||
<muxc:TabView.TabItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:ResultTabViewModel">
|
||||
<muxc:TabViewItem
|
||||
Header="{x:Bind Title}"
|
||||
IconSource="{x:Bind IconSource}">
|
||||
|
||||
<ScrollViewer
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ContentPresenter Content="{x:Bind Content}"/>
|
||||
</ScrollViewer>
|
||||
|
||||
<muxc:TabViewItem.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
Text="Экспорт"
|
||||
Command="{x:Bind ExportCommand}"
|
||||
Icon="Save"/>
|
||||
<MenuFlyoutItem
|
||||
Text="Копировать данные"
|
||||
Command="{x:Bind CopyDataCommand}"
|
||||
Icon="Copy"/>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem
|
||||
Text="Закрыть"
|
||||
Click="OnCloseTabClick"/>
|
||||
</MenuFlyout>
|
||||
</muxc:TabViewItem.ContextFlyout>
|
||||
</muxc:TabViewItem>
|
||||
</DataTemplate>
|
||||
</muxc:TabView.TabItemTemplate>
|
||||
</muxc:TabView>
|
||||
|
||||
<!-- Панель истории -->
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Visibility="{x:Bind ViewModel.History.Count, Converter={StaticResource CountToVisibilityConverter}, Mode=OneWay}">
|
||||
|
||||
<Expander
|
||||
Header="История выполненных запросов"
|
||||
IsExpanded="False"
|
||||
Margin="8">
|
||||
|
||||
<ListView
|
||||
ItemsSource="{x:Bind ViewModel.History, Mode=OneWay}"
|
||||
MaxHeight="200"
|
||||
SelectionMode="None">
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:ExecutionHistoryItem">
|
||||
<Grid Padding="8" BorderBrush="LightGray" BorderThickness="0,0,0,1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock
|
||||
Text="{x:Bind ScriptName}"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock
|
||||
Text="{x:Bind ExecutionTime, StringFormat='{}{0:dd.MM.yyyy HH:mm:ss}'}"
|
||||
FontSize="11"
|
||||
Foreground="Gray"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Text="{x:Bind Duration, StringFormat='{}{0:mm\\:ss}'}"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Text="{x:Bind RowCount, StringFormat='{}Строк: {0}'}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Статус бар -->
|
||||
<StatusBar Grid.Row="2">
|
||||
<StatusBarItem>
|
||||
<ProgressRing
|
||||
Width="16"
|
||||
Height="16"
|
||||
IsActive="{x:Bind ViewModel.IsBusy, Mode=OneWay}"/>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem>
|
||||
<TextBlock Text="{x:Bind ViewModel.StatusMessage, Mode=OneWay}"/>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Скриптов:"/>
|
||||
<Run Text="{x:Bind ViewModel.ScriptCategories.Sum(c => c.Scripts.Count), Mode=OneWay}"/>
|
||||
<Run Text="|"/>
|
||||
<Run Text="Вкладок:"/>
|
||||
<Run Text="{x:Bind ViewModel.ResultTabs.Count, Mode=OneWay}"/>
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
<!-- Прогресс выполнения -->
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Background="#CC000000"
|
||||
Visibility="{x:Bind ViewModel.IsBusy, Converter={StaticResource BoolToVisibility}, Mode=OneWay}">
|
||||
|
||||
<Border
|
||||
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<StackPanel Spacing="16" HorizontalAlignment="Center">
|
||||
<ProgressRing Width="40" Height="40" IsActive="True"/>
|
||||
<TextBlock
|
||||
Text="Выполнение запроса..."
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
169
SQLVision.UI/Views/MainWindow.xaml.cs
Normal file
169
SQLVision.UI/Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using SQLVision.Core.Models;
|
||||
using SQLVision.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace SQLVision.UI.Views
|
||||
{
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
// Õðàíèì òåêóùèå ìåòàäàííûå âûáðàííîãî ñêðèïòà
|
||||
private ScriptMetadata currentMetadata;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
SetWindowSize(1200, 800);
|
||||
LoadScripts();
|
||||
}
|
||||
|
||||
private void SetWindowSize(int width, int height)
|
||||
{
|
||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
var appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.Resize(new SizeInt32(width, height));
|
||||
}
|
||||
|
||||
private void LoadScripts()
|
||||
{
|
||||
var folder = Path.Combine(AppContext.BaseDirectory, "Scripts");
|
||||
if (!Directory.Exists(folder)) return;
|
||||
|
||||
foreach (var file in Directory.GetFiles(folder, "*.sql", SearchOption.AllDirectories))
|
||||
{
|
||||
var node = new TreeViewNode
|
||||
{
|
||||
Content = new ScriptTreeItem
|
||||
{
|
||||
DisplayName = Path.GetFileName(file),
|
||||
FilePath = file
|
||||
}
|
||||
};
|
||||
ScriptsTree.RootNodes.Add(node);
|
||||
}
|
||||
|
||||
ScriptsTree.ItemInvoked += ScriptsTree_ItemInvoked;
|
||||
}
|
||||
|
||||
private void ScriptsTree_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
|
||||
{
|
||||
if (args.InvokedItem is TreeViewNode node && node.Content is ScriptTreeItem item)
|
||||
{
|
||||
var file = item.FilePath;
|
||||
var lines = File.ReadAllLines(file);
|
||||
var parser = new SqlScriptParser();
|
||||
currentMetadata = parser.Parse(lines); // ñîõðàíÿåì â ïîëå
|
||||
|
||||
RenderParameters(currentMetadata);
|
||||
|
||||
OutputTabs.TabItems.Clear();
|
||||
foreach (var output in currentMetadata.Outputs)
|
||||
{
|
||||
var tab = new TabViewItem
|
||||
{
|
||||
Header = output.Description,
|
||||
Content = new TextBlock { Text = $"Âûâîä: {output.Type}" }
|
||||
};
|
||||
OutputTabs.TabItems.Add(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderParameters(ScriptMetadata metadata)
|
||||
{
|
||||
ParametersPanel.Items.Clear();
|
||||
|
||||
foreach (var param in metadata.Parameters)
|
||||
{
|
||||
FrameworkElement control = null;
|
||||
|
||||
switch (param.Type)
|
||||
{
|
||||
case ParameterType.Int:
|
||||
control = new TextBox { Header = param.Description, Text = param.DefaultValue ?? string.Empty };
|
||||
break;
|
||||
case ParameterType.String:
|
||||
control = new TextBox { Header = param.Description, Text = param.DefaultValue ?? string.Empty };
|
||||
break;
|
||||
case ParameterType.DateTime:
|
||||
control = new DatePicker
|
||||
{
|
||||
Header = param.Description,
|
||||
SelectedDate = DateTime.TryParse(param.DefaultValue, out var dt) ? dt : DateTime.Now
|
||||
};
|
||||
break;
|
||||
case ParameterType.Bool:
|
||||
control = new CheckBox
|
||||
{
|
||||
Content = param.Description,
|
||||
IsChecked = param.DefaultValue?.ToLower() == "true"
|
||||
};
|
||||
break;
|
||||
case ParameterType.Table:
|
||||
var combo = new ComboBox { Header = param.Description };
|
||||
combo.Items.Add("Ìàãàçèí 1");
|
||||
combo.Items.Add("Ìàãàçèí 2");
|
||||
combo.Items.Add("Ìàãàçèí 3");
|
||||
control = combo;
|
||||
break;
|
||||
}
|
||||
|
||||
param.Control = control;
|
||||
ParametersPanel.Items.Add(control);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, object> CollectParameterValues()
|
||||
{
|
||||
var values = new Dictionary<string, object>();
|
||||
|
||||
if (currentMetadata == null) return values;
|
||||
|
||||
foreach (var param in currentMetadata.Parameters)
|
||||
{
|
||||
switch (param.Type)
|
||||
{
|
||||
case ParameterType.Int:
|
||||
if (param.Control is TextBox tbInt && int.TryParse(tbInt.Text, out var intVal))
|
||||
values[param.Name] = intVal;
|
||||
break;
|
||||
case ParameterType.String:
|
||||
if (param.Control is TextBox tbStr)
|
||||
values[param.Name] = tbStr.Text;
|
||||
break;
|
||||
case ParameterType.DateTime:
|
||||
if (param.Control is DatePicker dp && dp.SelectedDate.HasValue)
|
||||
values[param.Name] = dp.SelectedDate.Value;
|
||||
break;
|
||||
case ParameterType.Bool:
|
||||
if (param.Control is CheckBox cb)
|
||||
values[param.Name] = cb.IsChecked ?? false;
|
||||
break;
|
||||
case ParameterType.Table:
|
||||
if (param.Control is ComboBox combo && combo.SelectedItem != null)
|
||||
values[param.Name] = combo.SelectedItem.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private void ExecuteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var values = CollectParameterValues();
|
||||
|
||||
// Çäåñü ìîæíî ñôîðìèðîâàòü SQL ñ ïîäñòàíîâêîé ïàðàìåòðîâ
|
||||
foreach (var kv in values)
|
||||
{
|
||||
Console.WriteLine($"{kv.Key} = {kv.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user