������� �� Avalonia 2.0 (�����-���������, ����������, �������� ��������) #1

Merged
Exest merged 18 commits from feature/avalonia-ui into master 2026-08-11 09:41:09 +03:00
7 changed files with 145 additions and 9 deletions
Showing only changes of commit 43468ac693 - Show all commits

View File

@ -123,8 +123,8 @@
<Setter Property="Padding" Value="0"/>
</Style>
<Style Selector="ListBox.topnav ListBoxItem">
<Setter Property="Padding" Value="10,7"/>
<Setter Property="Margin" Value="3,0"/>
<Setter Property="Padding" Value="6,7"/>
<Setter Property="Margin" Value="2,0"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Elfisa.Core;
namespace Elfisa.Avalonia.ViewModels;
/// <summary>«Загрузить» — скачивание актуального прайса с сервера в память.</summary>
public partial class LoadPriceViewModel : ViewModelBase
{
private readonly ApiClient _api = new();
[ObservableProperty] private bool _busy;
[ObservableProperty] private double _progress; // 0..100
[ObservableProperty] private string _progressText = "";
[ObservableProperty] private string? _result;
public string LastLoaded =>
PriceCache.LoadedAt is { } dt
? $"Последняя загрузка: {dt:dd.MM.yyyy HH:mm} — {PriceCache.Items.Count} позиций"
: "Прайс ещё не загружался в этой сессии.";
public LoadPriceViewModel()
{
_api.SetToken(Session.Current.Token);
}
[RelayCommand]
private async Task LoadAsync()
{
Result = null;
try
{
Busy = true;
Progress = 0;
ProgressText = "Соединение с сервером…";
var all = new List<PriceItem>();
const int page = 500;
int offset = 0, total = 0;
while (true)
{
var resp = await _api.GetPriceSummaryAsync(null, limit: page, offset: offset);
var batch = resp.Summary ?? new();
if (resp.TotalDrugs > 0) total = resp.TotalDrugs;
all.AddRange(batch);
Progress = total > 0 ? Math.Min(100.0, all.Count * 100.0 / total) : 0;
ProgressText = total > 0
? $"Загружено {all.Count} из {total} позиций…"
: $"Загружено {all.Count} позиций…";
offset += batch.Count;
if (batch.Count < page || (total > 0 && all.Count >= total) || offset > 30000)
break;
}
PriceCache.Items = all;
PriceCache.LoadedAt = DateTime.Now;
Progress = 100;
ProgressText = "Готово.";
var suppliers = all.Select(x => x.SupplierName).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().Count();
Result = $"Прайс загружен: {all.Count} позиций от {suppliers} поставщик(ов).";
OnPropertyChanged(nameof(LastLoaded));
}
catch (Exception ex)
{
Result = "Ошибка загрузки: " + ex.Message;
ProgressText = "";
}
finally { Busy = false; }
}
}

View File

@ -46,6 +46,7 @@ public partial class ShellViewModel : ViewModelBase
new("Заказы", "🛒", () => new OrdersViewModel()),
new("Накладные", "📄", () => new InvoicesViewModel()),
new("Отчёты", "📊", () => new ReportsViewModel()),
new("Загрузить", "📥", () => new LoadPriceViewModel()),
new("Отправить", "📤", () => new SendOrderViewModel()),
new("Отказы", "⛔", () => new PlaceholderPageViewModel("Отказы",
"Отказные позиции по заказам. Нужен серверный эндпоинт: данные (RefuseItemsCount) сейчас есть только в локальной БД WinForms-клиента.")),

View File

@ -0,0 +1,39 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Elfisa.Avalonia.ViewModels"
xmlns:conv="using:Avalonia.Data.Converters"
x:Class="Elfisa.Avalonia.Views.LoadPriceView"
x:DataType="vm:LoadPriceViewModel">
<Grid Margin="24" RowDefinitions="Auto,*">
<StackPanel Grid.Row="0" Spacing="2">
<TextBlock Text="Загрузить" Classes="h1"/>
<TextBlock Text="Скачать актуальный прайс с сервера" Classes="muted"/>
</StackPanel>
<Border Grid.Row="1" Classes="card" MaxWidth="560" Padding="30"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Spacing="18" HorizontalAlignment="Center">
<Border Width="60" Height="60" CornerRadius="30" Background="{DynamicResource AccentSoft}">
<TextBlock Text="📥" FontSize="26" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="{Binding LastLoaded}" Classes="muted" HorizontalAlignment="Center" TextAlignment="Center"/>
<Button Classes="primary" Content="Загрузить прайс" HorizontalAlignment="Center"
Padding="28,12" Command="{Binding LoadCommand}" IsEnabled="{Binding !Busy}"/>
<StackPanel Spacing="6" IsVisible="{Binding Busy}">
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress}" Height="8" Width="360"/>
<TextBlock Text="{Binding ProgressText}" Classes="muted" HorizontalAlignment="Center"/>
</StackPanel>
<Border Background="{DynamicResource AccentSoft}" CornerRadius="8" Padding="14,10"
IsVisible="{Binding Result, Converter={x:Static conv:ObjectConverters.IsNotNull}}">
<TextBlock Text="{Binding Result}" Foreground="{DynamicResource AccentPressed}"
TextWrapping="Wrap" TextAlignment="Center"/>
</Border>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,9 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Elfisa.Avalonia.Views;
public partial class LoadPriceView : UserControl
{
public LoadPriceView() => AvaloniaXamlLoader.Load(this);
}

View File

@ -11,9 +11,9 @@
<Grid ColumnDefinitions="Auto,*,Auto" Margin="18,0">
<!-- Логотип -->
<StackPanel Grid.Column="0" VerticalAlignment="Center" Margin="0,0,22,0">
<TextBlock Text="ЭльФиСА" FontSize="19" FontWeight="Bold" Foreground="White"/>
<TextBlock Text="Электронная Фармация" Foreground="{DynamicResource SidebarText}" FontSize="10.5"/>
<StackPanel Grid.Column="0" VerticalAlignment="Center" Margin="0,0,14,0">
<TextBlock Text="ЭльФиСА" FontSize="18" FontWeight="Bold" Foreground="White"/>
<TextBlock Text="Электронная Фармация" Foreground="{DynamicResource SidebarText}" FontSize="10"/>
</StackPanel>
<!-- Навигация -->
@ -26,10 +26,10 @@
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:NavItem">
<StackPanel Orientation="Vertical" Width="76" Spacing="3">
<TextBlock Text="{Binding Icon}" FontSize="17" HorizontalAlignment="Center"/>
<StackPanel Orientation="Vertical" Width="66" Spacing="2">
<TextBlock Text="{Binding Icon}" FontSize="16" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Title}" Foreground="{DynamicResource SidebarText}"
FontSize="11.5" HorizontalAlignment="Center"
FontSize="11" HorizontalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
</DataTemplate>
@ -37,7 +37,7 @@
</ListBox>
<!-- Пользователь + выход -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="14" VerticalAlignment="Center" Margin="18,0,0,0">
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center" Margin="8,0,0,0">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding UserName}" Foreground="White" FontSize="12" FontWeight="SemiBold"
HorizontalAlignment="Right" TextTrimming="CharacterEllipsis"/>

View File

@ -0,0 +1,9 @@
namespace Elfisa.Core;
/// <summary>Загруженный прайс в памяти (наполняется разделом «Загрузить»).</summary>
public static class PriceCache
{
public static List<PriceItem> Items { get; set; } = new();
public static DateTime? LoadedAt { get; set; }
public static bool HasData => Items.Count > 0;
}