forked from amkovkov/GranuSightSoftware2
feat: results exporting
This commit is contained in:
@@ -7,6 +7,10 @@ namespace GSS2.Core.Analysis.AmineContent;
|
|||||||
|
|
||||||
public static class ResultExporter
|
public static class ResultExporter
|
||||||
{
|
{
|
||||||
|
static ResultExporter()
|
||||||
|
{
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
}
|
||||||
public static void ExportCsv(string path, IEnumerable<ResultRecord> records)
|
public static void ExportCsv(string path, IEnumerable<ResultRecord> records)
|
||||||
{
|
{
|
||||||
NumberFormatInfo nfi = new NumberFormatInfo
|
NumberFormatInfo nfi = new NumberFormatInfo
|
||||||
@@ -75,31 +79,31 @@ public static class ResultExporter
|
|||||||
};
|
};
|
||||||
rows.Add(row);
|
rows.Add(row);
|
||||||
|
|
||||||
row = new List<string>(23);
|
row = Enumerable.Repeat("", 23).ToList();
|
||||||
row.Add("Нормированная обработанная площадь");
|
row.Add("Нормированная обработанная площадь");
|
||||||
foreach (var v in results.processedAreas)
|
foreach (var v in results.processedAreas)
|
||||||
row.Add(v.ToString("F3", nfi));
|
row.Add(v.ToString("F3", nfi));
|
||||||
rows.Add(row);
|
rows.Add(row);
|
||||||
|
|
||||||
row = new List<string>(23);
|
row = Enumerable.Repeat("", 23).ToList();
|
||||||
row.Add("Горячая площадь");
|
row.Add("Горячая площадь");
|
||||||
foreach (var v in results.hotspotAreas)
|
foreach (var v in results.hotspotAreas)
|
||||||
row.Add(v.ToString("F3", nfi));
|
row.Add(v.ToString("F3", nfi));
|
||||||
rows.Add(row);
|
rows.Add(row);
|
||||||
|
|
||||||
row = new List<string>(23);
|
row = Enumerable.Repeat("", 23).ToList();
|
||||||
row.Add("Оптический интегральный показатель");
|
row.Add("Оптический интегральный показатель");
|
||||||
foreach (var v in results.opticalIntegrals)
|
foreach (var v in results.opticalIntegrals)
|
||||||
row.Add(v.ToString("F3", nfi));
|
row.Add(v.ToString("F3", nfi));
|
||||||
rows.Add(row);
|
rows.Add(row);
|
||||||
|
|
||||||
row = new List<string>(23);
|
row = Enumerable.Repeat("", 23).ToList();
|
||||||
row.Add("Нормированная обработанная площадь");
|
row.Add("Нормированная обработанная площадь");
|
||||||
foreach (var v in results.innerStdDevs)
|
foreach (var v in results.innerStdDevs)
|
||||||
row.Add(v.ToString("F3", nfi));
|
row.Add(v.ToString("F3", nfi));
|
||||||
rows.Add(row);
|
rows.Add(row);
|
||||||
|
|
||||||
row = new List<string>(23);
|
row = Enumerable.Repeat("", 23).ToList();
|
||||||
row.Add("Внутригранульная неоднородность");
|
row.Add("Внутригранульная неоднородность");
|
||||||
foreach (var v in results.innerHeterogeneities)
|
foreach (var v in results.innerHeterogeneities)
|
||||||
row.Add(v.ToString("F3", nfi));
|
row.Add(v.ToString("F3", nfi));
|
||||||
@@ -117,6 +121,6 @@ public static class ResultExporter
|
|||||||
sb.Append("\n");
|
sb.Append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(path, sb.ToString());
|
File.WriteAllText(path, sb.ToString(), Encoding.GetEncoding(1251));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ public static class DependencyInjectionHelper
|
|||||||
return new CameraService(logger, serviceConfiguration, autoInitialize);
|
return new CameraService(logger, serviceConfiguration, autoInitialize);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
[Obsolete]
|
|
||||||
public static IServiceCollection AddUsbService(this IServiceCollection services) => services
|
public static IServiceCollection AddUsbService(this IServiceCollection services) => services
|
||||||
.AddSingleton<UsbService>();
|
.AddSingleton<UsbService>();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.0" />
|
||||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||||
|
|||||||
@@ -1,51 +1,192 @@
|
|||||||
using System.Device.I2c;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using GSS2.Core.Abstractions;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace GSS2.Core.Hardware;
|
namespace GSS2.Core.Hardware;
|
||||||
|
|
||||||
[Obsolete("Not implemented")]
|
|
||||||
public class UsbService : IDisposable
|
public class UsbService : IDisposable
|
||||||
{
|
{
|
||||||
private bool _disposedValue;
|
private bool _disposedValue;
|
||||||
private readonly ILogger<UsbService> _logger;
|
private readonly ILogger<UsbService> _logger;
|
||||||
private readonly Process _monitor;
|
|
||||||
|
|
||||||
public UsbService(ILogger<UsbService> logger)
|
public UsbService(ILogger<UsbService> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_logger.LogInformation("Инициализация");
|
|
||||||
|
|
||||||
_monitor = new Process();
|
|
||||||
_monitor.StartInfo = new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = "udisksctl",
|
|
||||||
Arguments = "monitor",
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true
|
|
||||||
};
|
|
||||||
_monitor.OutputDataReceived += MonitorDataReceived;
|
|
||||||
_monitor.ErrorDataReceived += MonitorDataReceived;
|
|
||||||
_monitor.Exited += (s, ea) => _logger.LogWarning("Монитор udisksctl отключен");
|
|
||||||
if (_monitor.Start())
|
|
||||||
_logger.LogInformation("Монитор udisksctl запущен");
|
|
||||||
else
|
|
||||||
_logger.LogError("Не удалось запустить монитор udisksctl");
|
|
||||||
_monitor.BeginOutputReadLine();
|
|
||||||
_monitor.BeginErrorReadLine();
|
|
||||||
|
|
||||||
_logger.LogInformation("Инициализировано");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MonitorDataReceived(object sender, DataReceivedEventArgs ea)
|
public List<(string id, string name, bool mounted, string? mountPath)> GetDevices()
|
||||||
{
|
{
|
||||||
if (ea.Data is null)
|
var devices = new List<(string name, string id, bool mounted, string? mountPath)>();
|
||||||
return;
|
try
|
||||||
_logger.LogInformation("Data rec {}", ea.Data);
|
{
|
||||||
|
_logger.LogInformation("Получаем список дисков");
|
||||||
|
var (exitCode, output, error) = RunCommand("lsblk", "-P -o NAME,PATH,RM,TYPE,MOUNTPOINT,LABEL,MODEL");
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Не удалось получить список дисков: {} {}", exitCode, error.Trim());
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var model = string.Empty;
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, string>();
|
||||||
|
var matches = Regex.Matches(line, @"(\w+)=""([^""]*)""");
|
||||||
|
foreach (Match m in matches)
|
||||||
|
dict[m.Groups[1].Value] = m.Groups[2].Value;
|
||||||
|
|
||||||
|
if (dict.TryGetValue("RM", out var rm) &&
|
||||||
|
dict.TryGetValue("TYPE", out var type) &&
|
||||||
|
rm == "1" &&
|
||||||
|
type == "disk")
|
||||||
|
{
|
||||||
|
model = dict.GetValueOrDefault("MODEL", "").Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dict.TryGetValue("RM", out rm) &&
|
||||||
|
dict.TryGetValue("TYPE", out type) &&
|
||||||
|
rm == "1" &&
|
||||||
|
type == "part")
|
||||||
|
{
|
||||||
|
var id = dict.GetValueOrDefault("PATH", "");
|
||||||
|
var mountpoint = dict.GetValueOrDefault("MOUNTPOINT", "");
|
||||||
|
var label = dict.GetValueOrDefault("LABEL", "").Trim();
|
||||||
|
var rawName = dict.GetValueOrDefault("NAME", "").Trim();
|
||||||
|
|
||||||
|
string displayName = string.Empty;
|
||||||
|
if (!string.IsNullOrWhiteSpace(label))
|
||||||
|
displayName = label;
|
||||||
|
if (!string.IsNullOrWhiteSpace(model))
|
||||||
|
displayName = displayName + (!string.IsNullOrWhiteSpace(displayName) ? " " : "") + model;
|
||||||
|
else
|
||||||
|
displayName = displayName + (!string.IsNullOrWhiteSpace(displayName) ? " " : "") + rawName;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(id))
|
||||||
|
devices.Add((id, displayName, !string.IsNullOrWhiteSpace(mountpoint), string.IsNullOrWhiteSpace(mountpoint) ? null : mountpoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Исключение при попытке получить список дисков");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Список дисков: \n\t{}", string.Join("\n\t", devices.Select(d => $"{d.name} {d.mounted} {d.id}")));
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? MountDevice(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Монтируем диск: {}", id);
|
||||||
|
var (exitCode, output, error) = RunCommand("udisksctl", $"mount -b {id} --no-user-interaction");
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Не удалось смонтировать диск {}: {} {}", id, exitCode, error.Trim());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Диск смонтирован {}: {}", id, output.Trim());
|
||||||
|
var result = output.Trim().Split("\n").Last();
|
||||||
|
if (result.StartsWith($"Mounted {id} at "))
|
||||||
|
{
|
||||||
|
var path = result.Substring($"Mounted {id} at ".Count());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Исключение при попытке смонтировать диск {}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string? GetMountPath(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Получаем информацию о диске: {}", id);
|
||||||
|
var (exitCode, output, error) = RunCommand("udisksctl", $"info -b {id}");
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Не удалось получить информацию о дисе {}: {} {}", id, exitCode, error.Trim());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Информация о диске {}: {}", id, output.Trim());
|
||||||
|
var result = output.Split("\n").FirstOrDefault(l => l.Trim().StartsWith("MountPoints:"));
|
||||||
|
if (result is null)
|
||||||
|
return null;
|
||||||
|
result = result.Trim().Substring("MountPoints:".Count()).Trim();
|
||||||
|
if (string.IsNullOrEmpty(result))
|
||||||
|
return null;
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Исключение при получении информации о диске {}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnmountDevice(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Размонтируем диск {}", id);
|
||||||
|
var (exitCode, output, error) = RunCommand("udisksctl", $"unmount -b {id} --no-user-interaction");
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
_logger.LogError("Не удалось размонтировать диск {}: {} {}", id, exitCode, error.Trim());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Диск размонтирован {}", id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Исключение при попытке размонтировать диск {}", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnmountAllDevices()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Размонтируем все диски");
|
||||||
|
var mountedDevices = GetDevices().Where(d => d.mounted).ToList();
|
||||||
|
|
||||||
|
foreach (var device in mountedDevices){
|
||||||
|
Console.WriteLine(device.id);
|
||||||
|
UnmountDevice(device.id);}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int exitCode, string output, string error) RunCommand(string fileName, string arguments)
|
||||||
|
{
|
||||||
|
using var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
Arguments = arguments,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
string output = process.StandardOutput.ReadToEnd();
|
||||||
|
string error = process.StandardError.ReadToEnd();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
return (process.ExitCode, output, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
@@ -53,13 +194,7 @@ public class UsbService : IDisposable
|
|||||||
if (!_disposedValue)
|
if (!_disposedValue)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
UnmountAllDevices();
|
||||||
try
|
|
||||||
{
|
|
||||||
_monitor.Close();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposedValue = true;
|
_disposedValue = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ public static class UI
|
|||||||
// builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
// builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
||||||
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
||||||
builder.Services.AddCameraService("Hardware:Camera", true);
|
builder.Services.AddCameraService("Hardware:Camera", true);
|
||||||
|
builder.Services.AddUsbService();
|
||||||
}
|
}
|
||||||
builder.Services.AddAmineContentImageCapturerService(mock);
|
builder.Services.AddAmineContentImageCapturerService(mock);
|
||||||
builder.Services.AddAmineContentAnalyzerService();
|
builder.Services.AddAmineContentAnalyzerService();
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ public static class DependencyInjectionHelper
|
|||||||
.AddSingleton<ViewModels.AmineContent.SampleCalibrationViewModel>()
|
.AddSingleton<ViewModels.AmineContent.SampleCalibrationViewModel>()
|
||||||
.AddSingleton<ViewModels.AmineContent.SeparatorCalibrationViewModel>()
|
.AddSingleton<ViewModels.AmineContent.SeparatorCalibrationViewModel>()
|
||||||
.AddSingleton<ViewModels.AmineContent.BrandCalibrationViewModel>()
|
.AddSingleton<ViewModels.AmineContent.BrandCalibrationViewModel>()
|
||||||
|
.AddSingleton<ViewModels.AmineContent.ExportViewModel>()
|
||||||
.AddSingleton<Views.AmineContent.StartView>()
|
.AddSingleton<Views.AmineContent.StartView>()
|
||||||
.AddSingleton<Views.AmineContent.AnalysisView>()
|
.AddSingleton<Views.AmineContent.AnalysisView>()
|
||||||
.AddSingleton<Views.AmineContent.SampleCalibrationView>()
|
.AddSingleton<Views.AmineContent.SampleCalibrationView>()
|
||||||
.AddSingleton<Views.AmineContent.SeparatorCalibrationView>()
|
.AddSingleton<Views.AmineContent.SeparatorCalibrationView>()
|
||||||
.AddSingleton<Views.AmineContent.BrandCalibrationView>();
|
.AddSingleton<Views.AmineContent.BrandCalibrationView>()
|
||||||
|
.AddSingleton<Views.AmineContent.ExportView>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
using GSS2.Core.Hardware;
|
||||||
|
using GSS2.Core.Analysis.AmineContent;
|
||||||
|
using GSS2.Core.Analysis.AmineContent.Database;
|
||||||
|
|
||||||
|
using GSS2.UI.Core.ViewModels;
|
||||||
|
using GSS2.UI.Core.Services;
|
||||||
|
|
||||||
|
namespace GSS2.ViewModels.AmineContent;
|
||||||
|
|
||||||
|
public partial class ExportViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public partial class UsbDiskViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
[ObservableProperty] public partial string Id { get; set; }
|
||||||
|
[ObservableProperty] public partial string Name { get; set; }
|
||||||
|
[ObservableProperty] public partial bool Mounted { get; set; }
|
||||||
|
[ObservableProperty] public partial string? MountPath { get; set; }
|
||||||
|
|
||||||
|
public UsbDiskViewModel(string id, string name, bool mounted, string mountPath)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Mounted = mounted;
|
||||||
|
MountPath = mountPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class RecordListViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
[ObservableProperty] public partial int Id { get; set; }
|
||||||
|
[ObservableProperty] public partial string SampleName { get; set; }
|
||||||
|
|
||||||
|
public RecordListViewModel(int id, string sampleName)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
SampleName = sampleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Логгер
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger<ExportViewModel> _logger;
|
||||||
|
/// <summary>
|
||||||
|
/// Контекст базы данных
|
||||||
|
/// </summary>
|
||||||
|
private readonly Context _context;
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис дисков
|
||||||
|
/// </summary>
|
||||||
|
private readonly UsbService _usb;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Диск смонтирован
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial bool DiskMounted { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Список дисков
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial ObservableCollection<UsbDiskViewModel> Disks { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Индекс выбранного диска
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial int DisksSelectedIndex { get; set; } = -1;
|
||||||
|
/// <summary>
|
||||||
|
/// Записи
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial ObservableCollection<RecordListViewModel> Records { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Выбранные для экспорта записи
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial ObservableCollection<RecordListViewModel> SelectedRecords { get; set; } = new();
|
||||||
|
/// <summary>
|
||||||
|
/// Путь точки монтирования
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial string? DiskMountPath { get; set; } = null;
|
||||||
|
/// <summary>
|
||||||
|
/// Оповещение отображено
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial bool IsNotificationVisible { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Текст оповещения
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial string NotificationMessage { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// Оповещение об удаче
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] public partial bool IsSuccessNotification { get; set; } = false;
|
||||||
|
|
||||||
|
public ExportViewModel(
|
||||||
|
ILogger<ExportViewModel> logger,
|
||||||
|
Context context,
|
||||||
|
UsbService usb
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Устанавливаем readonly поля
|
||||||
|
_logger = logger;
|
||||||
|
_context = context;
|
||||||
|
_usb = usb;
|
||||||
|
_logger.LogInformation("Инициализация");
|
||||||
|
|
||||||
|
// Получаем список дисков
|
||||||
|
Disks = new();
|
||||||
|
RefreshDisks();
|
||||||
|
|
||||||
|
// Получаем список результатов
|
||||||
|
Records = new ObservableCollection<RecordListViewModel>(_context.ResultRecords.Select(r => new RecordListViewModel(r.Id, r.SampleName)));
|
||||||
|
|
||||||
|
// Подписываемся на изменения базы данных для отслеживания изменений списка результатов
|
||||||
|
_context.ChangeTracker.StateChanged += (_, ea) =>
|
||||||
|
{
|
||||||
|
// Обновляем данные в интерфейсе
|
||||||
|
// Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
|
||||||
|
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
|
||||||
|
_ = Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
// Если запись является ResultRecord
|
||||||
|
if (ea.Entry.Entity is ResultRecord resultRecord)
|
||||||
|
{
|
||||||
|
// Если запись добавлена
|
||||||
|
if (ea.OldState is EntityState.Added && ea.NewState is EntityState.Unchanged)
|
||||||
|
{
|
||||||
|
// Добавляем запись
|
||||||
|
Records.Add(new RecordListViewModel(resultRecord.Id, resultRecord.SampleName));
|
||||||
|
}
|
||||||
|
// Если запись удалена
|
||||||
|
else if (ea.NewState is EntityState.Deleted)
|
||||||
|
{
|
||||||
|
// Ищем удалённую запись в списке
|
||||||
|
var deletedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id);
|
||||||
|
// Если запись не найдена в списке, то логгируем как ошибку
|
||||||
|
if (deletedItem is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Если удаляемый элемент выбран, то удаляем его из выбранных
|
||||||
|
if (SelectedRecords.Contains(deletedItem))
|
||||||
|
SelectedRecords.Remove(deletedItem);
|
||||||
|
// Удаляем элемент из списка
|
||||||
|
Records.Remove(deletedItem);
|
||||||
|
}
|
||||||
|
// Если запись изменилась
|
||||||
|
else if (ea.NewState is EntityState.Modified)
|
||||||
|
{
|
||||||
|
// Ищем изменённую запись в списке
|
||||||
|
var editedItem = Records.FirstOrDefault(r => r.Id == resultRecord.Id);
|
||||||
|
// Если запись не найдена в списке, то логгируем как ошибку
|
||||||
|
if (editedItem is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Не удалось найти элемент с Id({}) в списке {}", resultRecord.Id, nameof(Records));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editedItem.SampleName = resultRecord.SampleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Инициализировано");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обновить список дисков
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void RefreshDisks()
|
||||||
|
{
|
||||||
|
Disks.Clear();
|
||||||
|
var disks = _usb.GetDevices();
|
||||||
|
|
||||||
|
disks.ForEach(d => Disks.Add(new UsbDiskViewModel(d.id, d.name, d.mounted, d.mountPath)));
|
||||||
|
if (disks.Count() == 0 ||
|
||||||
|
DisksSelectedIndex >= disks.Count()
|
||||||
|
)
|
||||||
|
DisksSelectedIndex = -1;
|
||||||
|
if (DisksSelectedIndex == -1 &&
|
||||||
|
disks.Count() != 0
|
||||||
|
)
|
||||||
|
DisksSelectedIndex = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Смонтировать диск
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void MountDisk()
|
||||||
|
{
|
||||||
|
if (DiskMounted)
|
||||||
|
{
|
||||||
|
_logger.LogError("Диск уже смонтирован");
|
||||||
|
RefreshDisks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count())
|
||||||
|
{
|
||||||
|
_logger.LogError("Выбранный диск вне дисапзона");
|
||||||
|
RefreshDisks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var disk = Disks[DisksSelectedIndex];
|
||||||
|
if (disk.Mounted)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Выбранный диск уже смонтирован");
|
||||||
|
DiskMountPath = disk.MountPath;
|
||||||
|
DiskMounted = DiskMountPath is not null;
|
||||||
|
disk.Mounted = DiskMountPath is not null;
|
||||||
|
RefreshDisks();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DiskMountPath = _usb.MountDevice(disk.Id);
|
||||||
|
DiskMounted = DiskMountPath is not null;
|
||||||
|
disk.Mounted = DiskMountPath is not null;
|
||||||
|
RefreshDisks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Размонтировать диск
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void UnmountDisk()
|
||||||
|
{
|
||||||
|
if (!DiskMounted)
|
||||||
|
{
|
||||||
|
_logger.LogError("Диск не смонтирован");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DisksSelectedIndex < 0 || DisksSelectedIndex >= Disks.Count())
|
||||||
|
{
|
||||||
|
_logger.LogError("Выбранный диск вне дисапзона");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var disk = Disks[DisksSelectedIndex];
|
||||||
|
if (!disk.Mounted)
|
||||||
|
{
|
||||||
|
_logger.LogError("Выбранный диск не смонтирован");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = _usb.UnmountDevice(disk.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
DiskMountPath = null;
|
||||||
|
DiskMounted = false;
|
||||||
|
disk.Mounted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Экспортировать выбранные результаты в csv
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void ExportCsv()
|
||||||
|
{
|
||||||
|
if (!DiskMounted || DiskMountPath is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Диск не смонтирован");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}.csv";
|
||||||
|
var exportPath = Path.Join(DiskMountPath, fileName);
|
||||||
|
var exportingRecords = SelectedRecords.Select(r => _context.ResultRecords.Find(r.Id));
|
||||||
|
if (exportingRecords.Count() == 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Не выбраны записи для экспорта");
|
||||||
|
ShowNotification("Не выбраны записи для экспорта", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Количество записей для экспорта: {}", exportingRecords.Count());
|
||||||
|
ResultExporter.ExportCsv(exportPath, exportingRecords);
|
||||||
|
_logger.LogInformation("Результаты экспортированны в файл: {}", exportPath);
|
||||||
|
ShowNotification($"Файл успешно экспортирован:\n{fileName}", true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Исключение во время экспорта результатов");
|
||||||
|
ShowNotification("Ошибка при экспорте файла", false);
|
||||||
|
UnmountDisk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Запкрыть оповещение
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void DismissNotification()
|
||||||
|
{
|
||||||
|
IsNotificationVisible = false;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Экспортировать выбранные результаты в pdf
|
||||||
|
/// </summary>
|
||||||
|
// [RelayCommand]
|
||||||
|
// private void ExportPdf()
|
||||||
|
// {
|
||||||
|
// if (!DiskMounted || MountPath is null)
|
||||||
|
// {
|
||||||
|
// _logger.LogError("Диск не смонтирован");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var exportPath = Path.Join(MountPath, $"{DateTime.Now:ddMMyyyy_hhmmss}.csv");
|
||||||
|
// var exportingRecords = SelectedRecords.Select(r => _context.Find(r.Id));
|
||||||
|
// GSS2.Core.RecordsExporter.ExportPdf(exportPath, exportingRecords);
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// _logger.LogError(ex, "Исключение во время экспорта результатов");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
private void ShowNotification(string message, bool isSuccess)
|
||||||
|
{
|
||||||
|
NotificationMessage = message;
|
||||||
|
IsSuccessNotification = isSuccess;
|
||||||
|
IsNotificationVisible = true;
|
||||||
|
Task.Delay(5000).ContinueWith(_ => IsNotificationVisible = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public partial class AmineContentViewModel : ViewModelBase
|
|||||||
[ObservableProperty] public partial SeparatorCalibrationViewModel SeparatorCalibration { get; set; }
|
[ObservableProperty] public partial SeparatorCalibrationViewModel SeparatorCalibration { get; set; }
|
||||||
// [ObservableProperty] public partial CameraViewModel Camera { get; set; }
|
// [ObservableProperty] public partial CameraViewModel Camera { get; set; }
|
||||||
[ObservableProperty] public partial BrandCalibrationViewModel BrandCalibration { get; set; }
|
[ObservableProperty] public partial BrandCalibrationViewModel BrandCalibration { get; set; }
|
||||||
|
[ObservableProperty] public partial ExportViewModel Export { get; set; }
|
||||||
// [ObservableProperty] public partial SystemViewModel System { get; set; }
|
// [ObservableProperty] public partial SystemViewModel System { get; set; }
|
||||||
|
|
||||||
[ObservableProperty] public partial int CurrentIndex { get; set; } = 0;
|
[ObservableProperty] public partial int CurrentIndex { get; set; } = 0;
|
||||||
@@ -31,7 +32,8 @@ public partial class AmineContentViewModel : ViewModelBase
|
|||||||
AnalysisViewModel analysis,
|
AnalysisViewModel analysis,
|
||||||
SampleCalibrationViewModel sampleCalibration,
|
SampleCalibrationViewModel sampleCalibration,
|
||||||
SeparatorCalibrationViewModel separatorCalibration,
|
SeparatorCalibrationViewModel separatorCalibration,
|
||||||
BrandCalibrationViewModel brandCalibration
|
BrandCalibrationViewModel brandCalibration,
|
||||||
|
ExportViewModel export
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_navigationService = navigationService;
|
_navigationService = navigationService;
|
||||||
@@ -39,6 +41,7 @@ public partial class AmineContentViewModel : ViewModelBase
|
|||||||
SampleCalibration = sampleCalibration;
|
SampleCalibration = sampleCalibration;
|
||||||
SeparatorCalibration = separatorCalibration;
|
SeparatorCalibration = separatorCalibration;
|
||||||
BrandCalibration = brandCalibration;
|
BrandCalibration = brandCalibration;
|
||||||
|
Export = export;
|
||||||
|
|
||||||
PropertyChanged += (_, ea) =>
|
PropertyChanged += (_, ea) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="GSS2.Views.AmineContent.ExportView"
|
||||||
|
x:DataType="vm:ExportViewModel"
|
||||||
|
xmlns:controls="using:GSS2.Controls"
|
||||||
|
xmlns:core="using:GSS2.UI.Core"
|
||||||
|
xmlns:i="using:Avalonia.Xaml.Interactivity"
|
||||||
|
xmlns:ia="using:Avalonia.Xaml.Interactions.Custom"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:GSS2.ViewModels.AmineContent"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource MainBackground}">
|
||||||
|
<Grid IsVisible="{Binding !DiskMounted}">
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Подпись -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Выбор диска/Кнопка "Обновить список" -->
|
||||||
|
<RowDefinition Height="*" /> <!-- Пустое пространство -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Кнопка "Смонтировать диск" -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||||
|
|
||||||
|
<!-- Подпись -->
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="Выберите диск:"
|
||||||
|
/>
|
||||||
|
<!-- Выбор диска -->
|
||||||
|
<ComboBox
|
||||||
|
Classes="singleline"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsEditable="False"
|
||||||
|
ItemsSource="{Binding Disks}"
|
||||||
|
PlaceholderText="Диск не выбран"
|
||||||
|
SelectedIndex="{Binding DisksSelectedIndex}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ComboBox.Styles>
|
||||||
|
|
||||||
|
<Style Selector="ComboBox /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ComboBoxItem">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ComboBox.Styles>
|
||||||
|
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ExportViewModel+UsbDiskViewModel">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{Binding Name}" />
|
||||||
|
</TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<!-- Кнопка "Обновить список" -->
|
||||||
|
<Button
|
||||||
|
Command="{Binding RefreshDisksCommand}"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
>
|
||||||
|
<TextBlock Text="Обновить список"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Кнопка "Смонтировать диск" -->
|
||||||
|
<Button
|
||||||
|
Command="{Binding MountDiskCommand}"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="3"
|
||||||
|
IsEnabled="{Binding Path=DisksSelectedIndex, Converter={StaticResource GreaterThanOrEqualConverter}, ConverterParameter=0}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
>
|
||||||
|
<TextBlock Text="Смонтировать диск"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid IsVisible="{Binding DiskMounted}">
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Подпись -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Кнопки -->
|
||||||
|
<RowDefinition Height="*" /> <!-- Список -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Кнопки -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Примечение -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||||
|
|
||||||
|
<!-- Подпись -->
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="Выберите результатаы для экспорта:"
|
||||||
|
/>
|
||||||
|
<!-- Список -->
|
||||||
|
<ListBox
|
||||||
|
Background="{StaticResource MenuBackground}"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="2"
|
||||||
|
ItemsSource="{Binding Records}"
|
||||||
|
SelectionMode="Multiple,Toggle"
|
||||||
|
SelectedItems="{Binding SelectedRecords}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Шаблон списка элементов -->
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
|
||||||
|
<!-- Стили элементов -->
|
||||||
|
<ListBox.Styles>
|
||||||
|
|
||||||
|
<!-- Общий стиль -->
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="{StaticResource ButtonBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Стиль выбранного элемента -->
|
||||||
|
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource MainBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Стиль элемента над которым находится указатель -->
|
||||||
|
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource MainBackground}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ListBox.Styles>
|
||||||
|
|
||||||
|
<!-- Шаблон элемента -->
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ExportViewModel+RecordListViewModel">
|
||||||
|
<Border Margin="3" CornerRadius="5">
|
||||||
|
<TextBlock
|
||||||
|
Classes="mini"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
>
|
||||||
|
<Run Text="{Binding Id, StringFormat='[{0}] '}" />
|
||||||
|
<Run Text="{Binding SampleName}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<!-- Кнопка "Экспортировать CSV" -->
|
||||||
|
<Button
|
||||||
|
Command="{Binding ExportCsvCommand}"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="3"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
>
|
||||||
|
<TextBlock Text="Экспортировать CSV"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Кнопка "Размонтировать диск" -->
|
||||||
|
<Button
|
||||||
|
Command="{Binding UnmountDiskCommand}"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.Row="3"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
>
|
||||||
|
<TextBlock Text="Размонтировать диск"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="4"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Text="Перед извлечением диска обязательно размонтируйте его!"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Classes.success="{Binding IsSuccessNotification}"
|
||||||
|
Classes.error="{Binding !IsSuccessNotification}"
|
||||||
|
IsVisible="{Binding IsNotificationVisible}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Margin="15"
|
||||||
|
Padding="15,10"
|
||||||
|
CornerRadius="8"
|
||||||
|
BoxShadow="0 4 10 0 #40000000">
|
||||||
|
|
||||||
|
<Border.Styles>
|
||||||
|
<Style Selector="Border.success">
|
||||||
|
<Setter Property="Background" Value="#2E7D32" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.error">
|
||||||
|
<Setter Property="Background" Value="#C62828" />
|
||||||
|
</Style>
|
||||||
|
</Border.Styles>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*, Auto">
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{Binding NotificationMessage}"
|
||||||
|
Foreground="White"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
Grid.Column="1"
|
||||||
|
Command="{Binding DismissNotificationCommand}"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
Padding="12,8"
|
||||||
|
Background="#33FFFFFF"
|
||||||
|
Foreground="White"
|
||||||
|
CornerRadius="5">
|
||||||
|
<TextBlock Text="✕" FontSize="16" FontWeight="Bold"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace GSS2.Views.AmineContent;
|
||||||
|
|
||||||
|
public partial class ExportView : UserControl
|
||||||
|
{
|
||||||
|
public ExportView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Марки -->
|
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Марки -->
|
||||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Сепараторы -->
|
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Сепараторы -->
|
||||||
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Пробы -->
|
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Пробы -->
|
||||||
|
<ColumnDefinition Width="*" MinWidth="200"/> <!-- Экспорт -->
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Анализ -->
|
<!-- Анализ -->
|
||||||
@@ -81,6 +82,11 @@
|
|||||||
<Button Grid.Column="3" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="3">
|
<Button Grid.Column="3" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="3">
|
||||||
<TextBlock Classes="menu-text" Text="Пробы"/>
|
<TextBlock Classes="menu-text" Text="Пробы"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<!-- Экспорт -->
|
||||||
|
<Button Grid.Column="4" Classes="menu" Command="{Binding ChangeSectionCommand}" CommandParameter="4">
|
||||||
|
<TextBlock Classes="menu-text" Text="Экспорт"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
@@ -91,6 +97,7 @@
|
|||||||
<ContentControl Content="{Binding BrandCalibration}"/> <!-- Марки -->
|
<ContentControl Content="{Binding BrandCalibration}"/> <!-- Марки -->
|
||||||
<ContentControl Content="{Binding SeparatorCalibration}"/> <!-- Сепараторы -->
|
<ContentControl Content="{Binding SeparatorCalibration}"/> <!-- Сепараторы -->
|
||||||
<ContentControl Content="{Binding SampleCalibration}"/> <!-- Пробы -->
|
<ContentControl Content="{Binding SampleCalibration}"/> <!-- Пробы -->
|
||||||
|
<ContentControl Content="{Binding Export}"/> <!-- Экспорт -->
|
||||||
<!-- <ContentControl Content="{Binding Camera}"/> --> <!-- Камера -->
|
<!-- <ContentControl Content="{Binding Camera}"/> --> <!-- Камера -->
|
||||||
<!-- <ContentControl Content="{Binding System}"/> --> <!-- Система -->
|
<!-- <ContentControl Content="{Binding System}"/> --> <!-- Система -->
|
||||||
</core:SectionPanel>
|
</core:SectionPanel>
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user