������� �� Avalonia 2.0 (�����-���������, ����������, �������� ��������) #1
@ -135,6 +135,30 @@
|
|||||||
<Setter Property="Background" Value="{DynamicResource SidebarActive}"/>
|
<Setter Property="Background" Value="{DynamicResource SidebarActive}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============ Вкладки-документы ============ -->
|
||||||
|
<Style Selector="ListBox.tabs">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.tabs ListBoxItem">
|
||||||
|
<Setter Property="Padding" Value="12,6"/>
|
||||||
|
<Setter Property="Margin" Value="3,6"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Muted}"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.tabs ListBoxItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource RowAlt}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.tabs ListBoxItem:selected /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentSoft}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBox.tabs ListBoxItem:selected">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource AccentPressed}"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- ============ Таблица ============ -->
|
<!-- ============ Таблица ============ -->
|
||||||
<Style Selector="DataGrid">
|
<Style Selector="DataGrid">
|
||||||
<Setter Property="Background" Value="{DynamicResource Surface}"/>
|
<Setter Property="Background" Value="{DynamicResource Surface}"/>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Elfisa.Core;
|
using Elfisa.Core;
|
||||||
@ -10,7 +11,7 @@ namespace Elfisa.Avalonia.ViewModels;
|
|||||||
public class NavItem
|
public class NavItem
|
||||||
{
|
{
|
||||||
public string Title { get; }
|
public string Title { get; }
|
||||||
public string Icon { get; }
|
public string Icon { get; } // глиф Segoe MDL2 Assets
|
||||||
public Func<ViewModelBase> Factory { get; }
|
public Func<ViewModelBase> Factory { get; }
|
||||||
public NavItem(string title, string icon, Func<ViewModelBase> factory)
|
public NavItem(string title, string icon, Func<ViewModelBase> factory)
|
||||||
{
|
{
|
||||||
@ -18,14 +19,29 @@ public class NavItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Открытая вкладка-документ.</summary>
|
||||||
|
public sealed class DocumentTab
|
||||||
|
{
|
||||||
|
public string Title { get; }
|
||||||
|
public string Key { get; }
|
||||||
|
public ViewModelBase Content { get; }
|
||||||
|
public DocumentTab(string title, string key, ViewModelBase content)
|
||||||
|
{
|
||||||
|
Title = title; Key = key; Content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class ShellViewModel : ViewModelBase
|
public partial class ShellViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly Action _logout;
|
private readonly Action _logout;
|
||||||
|
private readonly ApiClient _api = new();
|
||||||
|
private bool _sync;
|
||||||
|
|
||||||
public ObservableCollection<NavItem> NavItems { get; }
|
public ObservableCollection<NavItem> NavItems { get; }
|
||||||
|
public ObservableCollection<DocumentTab> Tabs { get; } = new();
|
||||||
|
|
||||||
[ObservableProperty] private NavItem? _selectedNav;
|
[ObservableProperty] private NavItem? _selectedNav;
|
||||||
[ObservableProperty] private ViewModelBase? _currentPage;
|
[ObservableProperty] private DocumentTab? _activeTab;
|
||||||
|
|
||||||
public string UserName => Session.Current.Username ?? "";
|
public string UserName => Session.Current.Username ?? "";
|
||||||
public string RoleLabel => Session.Current.Role switch
|
public string RoleLabel => Session.Current.Role switch
|
||||||
@ -37,25 +53,84 @@ public partial class ShellViewModel : ViewModelBase
|
|||||||
_ => Session.Current.Role ?? ""
|
_ => Session.Current.Role ?? ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[ObservableProperty] private string _pharmacyAddress = "";
|
||||||
|
|
||||||
|
// Segoe MDL2 Assets — те же глифы, что в WinForms-версии
|
||||||
|
private const string IcoPrice = "";
|
||||||
|
private const string IcoOrders = "";
|
||||||
|
private const string IcoInvoices = "";
|
||||||
|
private const string IcoReports = "";
|
||||||
|
private const string IcoUpload = "";
|
||||||
|
private const string IcoSend = "";
|
||||||
|
private const string IcoRefusals = "";
|
||||||
|
private const string IcoDirectory = "";
|
||||||
|
|
||||||
public ShellViewModel(Action logout)
|
public ShellViewModel(Action logout)
|
||||||
{
|
{
|
||||||
_logout = logout;
|
_logout = logout;
|
||||||
|
_api.SetToken(Session.Current.Token);
|
||||||
|
|
||||||
NavItems = new ObservableCollection<NavItem>
|
NavItems = new ObservableCollection<NavItem>
|
||||||
{
|
{
|
||||||
new("Прайс-лист", "🧾", () => new PriceListViewModel()),
|
new("Прайс-лист", IcoPrice, () => new PriceListViewModel()),
|
||||||
new("Заказы", "🛒", () => new OrdersViewModel()),
|
new("Заказы", IcoOrders, () => new OrdersViewModel()),
|
||||||
new("Накладные", "📄", () => new InvoicesViewModel()),
|
new("Накладные", IcoInvoices, () => new InvoicesViewModel()),
|
||||||
new("Отчёты", "📊", () => new ReportsViewModel()),
|
new("Отчёты", IcoReports, () => new ReportsViewModel()),
|
||||||
new("Загрузить", "📥", () => new LoadPriceViewModel()),
|
new("Загрузить", IcoUpload, () => new LoadPriceViewModel()),
|
||||||
new("Отправить", "📤", () => new SendOrderViewModel()),
|
new("Отправить", IcoSend, () => new SendOrderViewModel()),
|
||||||
new("Отказы", "⛔", () => new PlaceholderPageViewModel("Отказы",
|
new("Отказы", IcoRefusals, () => new PlaceholderPageViewModel("Отказы",
|
||||||
"Отказные позиции по заказам. Нужен серверный эндпоинт: данные (RefuseItemsCount) сейчас есть только в локальной БД WinForms-клиента.")),
|
"Отказные позиции по заказам. Нужен серверный эндпоинт (RefuseItemsCount сейчас только в локальной БД WinForms).")),
|
||||||
new("Справочник", "📖", () => new DirectoryViewModel()),
|
new("Справочник", IcoDirectory, () => new DirectoryViewModel()),
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectedNav = NavItems.First(n => n.Title == "Отчёты");
|
SelectedNav = NavItems.First(n => n.Title == "Отчёты");
|
||||||
|
if (Session.Current.IsAuthenticated) _ = LoadPharmacyAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedNavChanged(NavItem? value) => CurrentPage = value?.Factory();
|
private async Task LoadPharmacyAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var locs = await _api.GetBuyerLocationsAsync();
|
||||||
|
var def = locs.FirstOrDefault(l => l.IsDefault) ?? locs.FirstOrDefault();
|
||||||
|
PharmacyAddress = def?.Address ?? "";
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedNavChanged(NavItem? value)
|
||||||
|
{
|
||||||
|
if (_sync || value is null) return;
|
||||||
|
OpenOrActivate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenOrActivate(NavItem nav)
|
||||||
|
{
|
||||||
|
var existing = Tabs.FirstOrDefault(t => t.Key == nav.Title);
|
||||||
|
if (existing is not null) { ActiveTab = existing; return; }
|
||||||
|
var tab = new DocumentTab(nav.Title, nav.Title, nav.Factory());
|
||||||
|
Tabs.Add(tab);
|
||||||
|
ActiveTab = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnActiveTabChanged(DocumentTab? value)
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
_sync = true;
|
||||||
|
SelectedNav = NavItems.FirstOrDefault(n => n.Title == value.Key);
|
||||||
|
_sync = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CloseTab(DocumentTab? tab)
|
||||||
|
{
|
||||||
|
if (tab is null) return;
|
||||||
|
int idx = Tabs.IndexOf(tab);
|
||||||
|
bool wasActive = ReferenceEquals(ActiveTab, tab);
|
||||||
|
Tabs.Remove(tab);
|
||||||
|
if (wasActive)
|
||||||
|
ActiveTab = Tabs.Count == 0 ? null : Tabs[Math.Min(idx, Tabs.Count - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand] private void Logout() => _logout();
|
[RelayCommand] private void Logout() => _logout();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,17 +6,15 @@
|
|||||||
|
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
|
|
||||||
<!-- Верхняя панель навигации (как в исходной программе) -->
|
<!-- Верхняя панель навигации -->
|
||||||
<Border DockPanel.Dock="Top" Background="{DynamicResource Sidebar}" Height="82">
|
<Border DockPanel.Dock="Top" Background="{DynamicResource Sidebar}" Height="82">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="18,0">
|
<Grid ColumnDefinitions="Auto,*,Auto" Margin="18,0">
|
||||||
|
|
||||||
<!-- Логотип -->
|
|
||||||
<StackPanel Grid.Column="0" VerticalAlignment="Center" Margin="0,0,14,0">
|
<StackPanel Grid.Column="0" VerticalAlignment="Center" Margin="0,0,14,0">
|
||||||
<TextBlock Text="ЭльФиСА" FontSize="18" FontWeight="Bold" Foreground="White"/>
|
<TextBlock Text="ЭльФиСА" FontSize="18" FontWeight="Bold" Foreground="White"/>
|
||||||
<TextBlock Text="Электронная Фармация" Foreground="{DynamicResource SidebarText}" FontSize="10"/>
|
<TextBlock Text="Электронная Фармация" Foreground="{DynamicResource SidebarText}" FontSize="10"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Навигация -->
|
|
||||||
<ListBox Grid.Column="1" Classes="topnav" VerticalAlignment="Center" HorizontalAlignment="Left"
|
<ListBox Grid.Column="1" Classes="topnav" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||||
ItemsSource="{Binding NavItems}" SelectedItem="{Binding SelectedNav}">
|
ItemsSource="{Binding NavItems}" SelectedItem="{Binding SelectedNav}">
|
||||||
<ListBox.ItemsPanel>
|
<ListBox.ItemsPanel>
|
||||||
@ -26,17 +24,16 @@
|
|||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:NavItem">
|
<DataTemplate x:DataType="vm:NavItem">
|
||||||
<StackPanel Orientation="Vertical" Width="66" Spacing="2">
|
<StackPanel Orientation="Vertical" Width="66" Spacing="3">
|
||||||
<TextBlock Text="{Binding Icon}" FontSize="16" HorizontalAlignment="Center"/>
|
<TextBlock Text="{Binding Icon}" FontFamily="Segoe MDL2 Assets" FontSize="18"
|
||||||
|
Foreground="White" HorizontalAlignment="Center"/>
|
||||||
<TextBlock Text="{Binding Title}" Foreground="{DynamicResource SidebarText}"
|
<TextBlock Text="{Binding Title}" Foreground="{DynamicResource SidebarText}"
|
||||||
FontSize="11" HorizontalAlignment="Center"
|
FontSize="11" HorizontalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||||
TextTrimming="CharacterEllipsis"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|
||||||
<!-- Пользователь + выход -->
|
|
||||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center" Margin="8,0,0,0">
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center" Margin="8,0,0,0">
|
||||||
<StackPanel VerticalAlignment="Center">
|
<StackPanel VerticalAlignment="Center">
|
||||||
<TextBlock Text="{Binding UserName}" Foreground="White" FontSize="12" FontWeight="SemiBold"
|
<TextBlock Text="{Binding UserName}" Foreground="White" FontSize="12" FontWeight="SemiBold"
|
||||||
@ -44,10 +41,6 @@
|
|||||||
<TextBlock Text="{Binding RoleLabel}" Foreground="{DynamicResource SidebarText}"
|
<TextBlock Text="{Binding RoleLabel}" Foreground="{DynamicResource SidebarText}"
|
||||||
FontSize="10.5" HorizontalAlignment="Right"/>
|
FontSize="10.5" HorizontalAlignment="Right"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Content="⚙" Click="OnSettingsClick" ToolTip.Tip="Настройки"
|
|
||||||
Background="Transparent" Foreground="White" BorderBrush="#55FFFFFF"
|
|
||||||
BorderThickness="1" CornerRadius="8" Width="40" Height="38" Padding="0"
|
|
||||||
FontSize="16" Cursor="Hand"/>
|
|
||||||
<Button Content="Выйти" Command="{Binding LogoutCommand}"
|
<Button Content="Выйти" Command="{Binding LogoutCommand}"
|
||||||
Background="Transparent" Foreground="White" BorderBrush="#55FFFFFF"
|
Background="Transparent" Foreground="White" BorderBrush="#55FFFFFF"
|
||||||
BorderThickness="1" CornerRadius="8" Padding="16,9" Cursor="Hand"/>
|
BorderThickness="1" CornerRadius="8" Padding="16,9" Cursor="Hand"/>
|
||||||
@ -55,7 +48,48 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Контент -->
|
<!-- Строка вкладок-документов -->
|
||||||
<ContentControl Content="{Binding CurrentPage}"/>
|
<Border DockPanel.Dock="Top" Background="{DynamicResource Surface}"
|
||||||
|
BorderBrush="{DynamicResource Border}" BorderThickness="0,0,0,1" MinHeight="44" Padding="10,0">
|
||||||
|
<ListBox Classes="tabs" VerticalAlignment="Center" x:CompileBindings="False"
|
||||||
|
ItemsSource="{Binding Tabs}" SelectedItem="{Binding ActiveTab}">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:DocumentTab">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Title}" VerticalAlignment="Center" FontSize="13"/>
|
||||||
|
<Button Content="✕" FontSize="11" Width="20" Height="20" Padding="0"
|
||||||
|
Background="Transparent" BorderThickness="0" Cursor="Hand"
|
||||||
|
Foreground="{DynamicResource Muted}"
|
||||||
|
Command="{Binding $parent[ListBox].DataContext.CloseTabCommand}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Нижняя строка состояния -->
|
||||||
|
<Border DockPanel.Dock="Bottom" Background="{DynamicResource Surface}"
|
||||||
|
BorderBrush="{DynamicResource Border}" BorderThickness="0,1,0,0" Height="32" Padding="16,0">
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="Пользователь:" Classes="muted" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding UserName}" FontSize="12" Foreground="{DynamicResource Text}"/>
|
||||||
|
<TextBlock Text=" | Аптека:" Classes="muted" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding PharmacyAddress}" FontSize="12" Foreground="{DynamicResource Text}" TextTrimming="CharacterEllipsis"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Grid.Column="1" Content="Настройки" Click="OnSettingsClick"
|
||||||
|
Background="Transparent" Foreground="{DynamicResource Accent}" BorderThickness="0"
|
||||||
|
Padding="10,4" FontSize="12.5" Cursor="Hand"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Контент активной вкладки -->
|
||||||
|
<ContentControl Content="{Binding ActiveTab.Content}"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user