Avalonia: верхняя навигация (как в WinForms) + раздел «Заказы»
- Навигация перенесена из левого сайдбара в ВЕРХНЮЮ горизонтальную панель, как в исходной программе: логотип слева, вкладки (иконка+подпись) по центру, пользователь+Выйти справа - Раздел «Заказы»: мастер-детали (список слева, состав заказа справа с позициями и итогом), на данных /api/buyer/report - Числовые колонки выровнены вправо (стиль DataGridCell.num) - Отчёты: тулбар на WrapPanel, стабильные ширины колонок Проверено запуском (скриншоты): верхняя навигация, отчёты, заказы. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f6a1012e9b
commit
132ce1531f
@ -116,6 +116,25 @@
|
|||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============ Верхняя навигация (как в WinForms-версии) ============ -->
|
||||||
|
<Style Selector="ListBox.topnav">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<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="CornerRadius" Value="10"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.topnav ListBoxItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SidebarActive}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.topnav ListBoxItem:selected /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SidebarActive}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- ============ Таблица ============ -->
|
<!-- ============ Таблица ============ -->
|
||||||
<Style Selector="DataGrid">
|
<Style Selector="DataGrid">
|
||||||
<Setter Property="Background" Value="{DynamicResource Surface}"/>
|
<Setter Property="Background" Value="{DynamicResource Surface}"/>
|
||||||
@ -139,6 +158,10 @@
|
|||||||
<Style Selector="DataGridCell">
|
<Style Selector="DataGridCell">
|
||||||
<Setter Property="Padding" Value="10,0"/>
|
<Setter Property="Padding" Value="10,0"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<!-- числовые ячейки — вправо -->
|
||||||
|
<Style Selector="DataGridCell.num TextBlock">
|
||||||
|
<Setter Property="TextAlignment" Value="Right"/>
|
||||||
|
</Style>
|
||||||
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
|
||||||
<Setter Property="Fill" Value="{DynamicResource AccentSoft}"/>
|
<Setter Property="Fill" Value="{DynamicResource AccentSoft}"/>
|
||||||
<Setter Property="Opacity" Value="1"/>
|
<Setter Property="Opacity" Value="1"/>
|
||||||
|
|||||||
99
src/Elfisa.Avalonia/ViewModels/OrdersViewModel.cs
Normal file
99
src/Elfisa.Avalonia/ViewModels/OrdersViewModel.cs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Elfisa.Core;
|
||||||
|
|
||||||
|
namespace Elfisa.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
public sealed class OrderItemRow
|
||||||
|
{
|
||||||
|
public string Name { get; init; } = "";
|
||||||
|
public string Code { get; init; } = "";
|
||||||
|
public decimal Qty { get; init; }
|
||||||
|
public decimal Price { get; init; }
|
||||||
|
public decimal Sum { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class OrderSummary
|
||||||
|
{
|
||||||
|
public string Date { get; init; } = "";
|
||||||
|
public string Number { get; init; } = "";
|
||||||
|
public string Supplier { get; init; } = "";
|
||||||
|
public string Location { get; init; } = "";
|
||||||
|
public string Status { get; init; } = "";
|
||||||
|
public int ItemsCount { get; init; }
|
||||||
|
public decimal Sum { get; init; }
|
||||||
|
public ObservableCollection<OrderItemRow> Items { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Заказы: список + состав. Пока строится из /api/buyer/report (он работает);
|
||||||
|
/// после деплоя фикса /api/buyer/orders можно перейти на «родной» эндпоинт
|
||||||
|
/// (даст ещё PlacedAt, Comment, отмену/размещение).
|
||||||
|
/// </summary>
|
||||||
|
public partial class OrdersViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ApiClient _api = new();
|
||||||
|
|
||||||
|
public ObservableCollection<OrderSummary> Orders { get; } = new();
|
||||||
|
|
||||||
|
[ObservableProperty] private OrderSummary? _selectedOrder;
|
||||||
|
[ObservableProperty] private bool _busy;
|
||||||
|
[ObservableProperty] private string? _status;
|
||||||
|
|
||||||
|
public OrdersViewModel()
|
||||||
|
{
|
||||||
|
_api.SetToken(Session.Current.Token);
|
||||||
|
if (Session.Current.IsAuthenticated) _ = LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task LoadAsync()
|
||||||
|
{
|
||||||
|
Status = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Busy = true;
|
||||||
|
var to = DateTime.Today;
|
||||||
|
var from = to.AddMonths(-24);
|
||||||
|
var lines = await _api.GetBuyerReportAsync(from, to);
|
||||||
|
|
||||||
|
Orders.Clear();
|
||||||
|
foreach (var g in lines.GroupBy(l => l.OrderId)
|
||||||
|
.OrderByDescending(x => x.First().OrderDate ?? DateTime.MinValue))
|
||||||
|
{
|
||||||
|
var f = g.First();
|
||||||
|
var items = new ObservableCollection<OrderItemRow>(
|
||||||
|
g.Select(l => new OrderItemRow
|
||||||
|
{
|
||||||
|
Name = l.ItemName ?? "",
|
||||||
|
Code = l.ItemCode ?? "",
|
||||||
|
Qty = l.Qty,
|
||||||
|
Price = l.UnitPrice,
|
||||||
|
Sum = l.Sum
|
||||||
|
}));
|
||||||
|
|
||||||
|
Orders.Add(new OrderSummary
|
||||||
|
{
|
||||||
|
Date = f.OrderDate?.ToLocalTime().ToString("dd.MM.yyyy") ?? "",
|
||||||
|
Number = f.GlobalSign ?? "",
|
||||||
|
Supplier = string.Join(", ", g.Select(x => x.Supplier)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()),
|
||||||
|
Location = f.Location ?? "",
|
||||||
|
Status = f.Status ?? "",
|
||||||
|
ItemsCount = g.Count(),
|
||||||
|
Sum = g.Sum(x => x.Sum),
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedOrder = Orders.FirstOrDefault();
|
||||||
|
if (Orders.Count == 0) Status = "Заказов за последние 2 года нет.";
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Status = ex.Message; }
|
||||||
|
finally { Busy = false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,13 +42,13 @@ public partial class ShellViewModel : ViewModelBase
|
|||||||
_logout = logout;
|
_logout = logout;
|
||||||
NavItems = new ObservableCollection<NavItem>
|
NavItems = new ObservableCollection<NavItem>
|
||||||
{
|
{
|
||||||
new("Прайс-лист", "P", () => new PlaceholderPageViewModel("Прайс-лист", "Каталог, поиск и добавление позиций в заказ.")),
|
new("Прайс-лист", "🧾", () => new PlaceholderPageViewModel("Прайс-лист", "Каталог, поиск и добавление позиций в заказ.")),
|
||||||
new("Заказы", "З", () => new PlaceholderPageViewModel("Заказы", "История заказов и их статусы.")),
|
new("Заказы", "🛒", () => new OrdersViewModel()),
|
||||||
new("Накладные", "Н", () => new PlaceholderPageViewModel("Накладные", "Приход товара по заказам.")),
|
new("Накладные", "📄", () => new PlaceholderPageViewModel("Накладные", "Приход товара по заказам.")),
|
||||||
new("Отчёты", "О", () => new ReportsViewModel()),
|
new("Отчёты", "📊", () => new ReportsViewModel()),
|
||||||
new("Отправить", "→", () => new PlaceholderPageViewModel("Отправить", "Формирование и выгрузка заявок поставщику (DBF).")),
|
new("Отправить", "📤", () => new PlaceholderPageViewModel("Отправить", "Формирование и выгрузка заявок поставщику (DBF).")),
|
||||||
new("Отказы", "!", () => new PlaceholderPageViewModel("Отказы", "Отказные позиции по заказам.")),
|
new("Отказы", "⛔", () => new PlaceholderPageViewModel("Отказы", "Отказные позиции по заказам.")),
|
||||||
new("Справочник", "С", () => new PlaceholderPageViewModel("Справочник", "Контрагенты, грузополучатели, настройки.")),
|
new("Справочник", "📖", () => new PlaceholderPageViewModel("Справочник", "Контрагенты, грузополучатели, настройки.")),
|
||||||
};
|
};
|
||||||
SelectedNav = NavItems.First(n => n.Title == "Отчёты");
|
SelectedNav = NavItems.First(n => n.Title == "Отчёты");
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/Elfisa.Avalonia/Views/OrdersView.axaml
Normal file
75
src/Elfisa.Avalonia/Views/OrdersView.axaml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<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.OrdersView"
|
||||||
|
x:DataType="vm:OrdersViewModel">
|
||||||
|
|
||||||
|
<Grid Margin="24" RowDefinitions="Auto,*">
|
||||||
|
<StackPanel Grid.Row="0" Spacing="2" Margin="0,0,0,14">
|
||||||
|
<TextBlock Text="Заказы" Classes="h1"/>
|
||||||
|
<TextBlock Text="История заказов и их состав" Classes="muted"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="*,16,430">
|
||||||
|
|
||||||
|
<!-- Список заказов -->
|
||||||
|
<Border Grid.Column="0" Classes="card" Padding="0" ClipToBounds="True">
|
||||||
|
<DataGrid ItemsSource="{Binding Orders}" SelectedItem="{Binding SelectedOrder}"
|
||||||
|
x:CompileBindings="False"
|
||||||
|
AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
|
||||||
|
CanUserResizeColumns="True" CanUserSortColumns="False">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Дата" Width="112" Binding="{Binding Date}"/>
|
||||||
|
<DataGridTextColumn Header="Номер" Width="140" Binding="{Binding Number}"/>
|
||||||
|
<DataGridTextColumn Header="Поставщик" Width="*" Binding="{Binding Supplier}"/>
|
||||||
|
<DataGridTextColumn Header="Позиций" Width="90" Binding="{Binding ItemsCount}" CellStyleClasses="num"/>
|
||||||
|
<DataGridTextColumn Header="Сумма" Width="132" Binding="{Binding Sum, StringFormat='{}{0:N2}'}" CellStyleClasses="num"/>
|
||||||
|
<DataGridTextColumn Header="Статус" Width="95" Binding="{Binding Status}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Карточка заказа -->
|
||||||
|
<Border Grid.Column="2" Classes="card" Padding="18">
|
||||||
|
<DockPanel>
|
||||||
|
<StackPanel DockPanel.Dock="Top" Spacing="4" Margin="0,0,0,14">
|
||||||
|
<TextBlock Text="{Binding SelectedOrder.Number}" Classes="h2"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<TextBlock Text="{Binding SelectedOrder.Date}" Classes="muted"/>
|
||||||
|
<TextBlock Text="{Binding SelectedOrder.Status}" Classes="muted"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="{Binding SelectedOrder.Supplier}" Classes="muted" TextWrapping="Wrap"/>
|
||||||
|
<TextBlock Text="{Binding SelectedOrder.Location}" Classes="muted" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Border DockPanel.Dock="Bottom" Margin="0,12,0,0" Padding="0,10,0,0"
|
||||||
|
BorderThickness="0,1,0,0" BorderBrush="{DynamicResource Border}">
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock Grid.Column="0" Text="Итого" FontWeight="SemiBold" Foreground="{DynamicResource Text}"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding SelectedOrder.Sum, StringFormat='{}{0:N2}'}"
|
||||||
|
FontWeight="SemiBold" Foreground="{DynamicResource Accent}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<DataGrid ItemsSource="{Binding SelectedOrder.Items}" x:CompileBindings="False"
|
||||||
|
AutoGenerateColumns="False" IsReadOnly="True" HeadersVisibility="Column"
|
||||||
|
CanUserResizeColumns="True" CanUserSortColumns="False">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Товар" Width="*" Binding="{Binding Name}"/>
|
||||||
|
<DataGridTextColumn Header="Кол-во" Width="78" Binding="{Binding Qty, StringFormat='{}{0:N0}'}" CellStyleClasses="num"/>
|
||||||
|
<DataGridTextColumn Header="Сумма" Width="110" Binding="{Binding Sum, StringFormat='{}{0:N2}'}" CellStyleClasses="num"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Статус -->
|
||||||
|
<Border Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="4"
|
||||||
|
Padding="10,7" CornerRadius="6" Background="{DynamicResource AccentSoft}"
|
||||||
|
IsVisible="{Binding Status, Converter={x:Static conv:ObjectConverters.IsNotNull}}">
|
||||||
|
<TextBlock Text="{Binding Status}" Foreground="{DynamicResource AccentPressed}" FontSize="12.5"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
9
src/Elfisa.Avalonia/Views/OrdersView.axaml.cs
Normal file
9
src/Elfisa.Avalonia/Views/OrdersView.axaml.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Elfisa.Avalonia.Views;
|
||||||
|
|
||||||
|
public partial class OrdersView : UserControl
|
||||||
|
{
|
||||||
|
public OrdersView() => AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
@ -4,47 +4,54 @@
|
|||||||
x:Class="Elfisa.Avalonia.Views.ShellView"
|
x:Class="Elfisa.Avalonia.Views.ShellView"
|
||||||
x:DataType="vm:ShellViewModel">
|
x:DataType="vm:ShellViewModel">
|
||||||
|
|
||||||
<Grid ColumnDefinitions="250,*">
|
|
||||||
|
|
||||||
<!-- Сайдбар -->
|
|
||||||
<Border Grid.Column="0" Background="{DynamicResource Sidebar}">
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<StackPanel DockPanel.Dock="Top" Margin="22,24,20,18" Spacing="2">
|
|
||||||
<TextBlock Text="ЭльФиСА" FontSize="21" FontWeight="Bold" Foreground="White"/>
|
<!-- Верхняя панель навигации (как в исходной программе) -->
|
||||||
<TextBlock Text="Электронная Фармация" Foreground="{DynamicResource SidebarText}" FontSize="11"/>
|
<Border DockPanel.Dock="Top" Background="{DynamicResource Sidebar}" Height="82">
|
||||||
|
<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>
|
</StackPanel>
|
||||||
|
|
||||||
<Border DockPanel.Dock="Bottom" Margin="14,10,14,16" Padding="14" CornerRadius="12"
|
<!-- Навигация -->
|
||||||
Background="{DynamicResource SidebarActive}">
|
<ListBox Grid.Column="1" Classes="topnav" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||||
<StackPanel Spacing="9">
|
|
||||||
<TextBlock Text="{Binding UserName}" Foreground="White" FontWeight="SemiBold"
|
|
||||||
FontSize="12.5" TextTrimming="CharacterEllipsis"/>
|
|
||||||
<TextBlock Text="{Binding RoleLabel}" Foreground="{DynamicResource SidebarText}" FontSize="11"/>
|
|
||||||
<Button Content="Выйти" Command="{Binding LogoutCommand}" HorizontalAlignment="Stretch"
|
|
||||||
Background="Transparent" Foreground="White" BorderBrush="#55FFFFFF"
|
|
||||||
BorderThickness="1" CornerRadius="8" Padding="0,7" Cursor="Hand"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<ListBox Classes="nav" Margin="0,6,0,0"
|
|
||||||
ItemsSource="{Binding NavItems}" SelectedItem="{Binding SelectedNav}">
|
ItemsSource="{Binding NavItems}" SelectedItem="{Binding SelectedNav}">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:NavItem">
|
<DataTemplate x:DataType="vm:NavItem">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
<StackPanel Orientation="Vertical" Width="76" Spacing="3">
|
||||||
<Border Width="26" Height="26" CornerRadius="7" Background="#26FFFFFF">
|
<TextBlock Text="{Binding Icon}" FontSize="17" HorizontalAlignment="Center"/>
|
||||||
<TextBlock Text="{Binding Icon}" Foreground="White" FontSize="12.5"
|
<TextBlock Text="{Binding Title}" Foreground="{DynamicResource SidebarText}"
|
||||||
FontWeight="SemiBold"
|
FontSize="11.5" HorizontalAlignment="Center"
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
TextTrimming="CharacterEllipsis"/>
|
||||||
</Border>
|
|
||||||
<TextBlock Text="{Binding Title}" VerticalAlignment="Center"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</DockPanel>
|
|
||||||
|
<!-- Пользователь + выход -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="14" VerticalAlignment="Center" Margin="18,0,0,0">
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding UserName}" Foreground="White" FontSize="12" FontWeight="SemiBold"
|
||||||
|
HorizontalAlignment="Right" TextTrimming="CharacterEllipsis"/>
|
||||||
|
<TextBlock Text="{Binding RoleLabel}" Foreground="{DynamicResource SidebarText}"
|
||||||
|
FontSize="10.5" HorizontalAlignment="Right"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Content="Выйти" Command="{Binding LogoutCommand}"
|
||||||
|
Background="Transparent" Foreground="White" BorderBrush="#55FFFFFF"
|
||||||
|
BorderThickness="1" CornerRadius="8" Padding="16,9" Cursor="Hand"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Контент -->
|
<!-- Контент -->
|
||||||
<ContentControl Grid.Column="1" Content="{Binding CurrentPage}"/>
|
<ContentControl Content="{Binding CurrentPage}"/>
|
||||||
</Grid>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user