Добавьте файлы проекта.

This commit is contained in:
FrigaT
2026-01-05 00:29:19 +03:00
committed by FrigaT
parent 76a09d80d4
commit d0653c2098
105 changed files with 6729 additions and 0 deletions

View 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"];
}
}

View 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>

View 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);
}
}

View 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;
}
}
}

View 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()
{
// Копирование данных в буфер обмена
}
}

View 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);
}
}

View 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>

View 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}");
}
}
}
}