================================================================================
ПРОЕКТ: AvaloniaApplication1 (Магазин обуви Botinki)
================================================================================
--------------------------------------------------------------------------------
ФАЙЛ: AvaloniaApplication1.csproj
--------------------------------------------------------------------------------
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
<RootNamespace>AvaloniaApplication1</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.11" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
<PackageReference Include="Npgsql" Version="8.0.5" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**\*" />
<None Include="Assets\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
--------------------------------------------------------------------------------
ФАЙЛ: Program.cs
--------------------------------------------------------------------------------
using System;
using Avalonia;
namespace AvaloniaApplication1;
internal static class Program
{
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
--------------------------------------------------------------------------------
ФАЙЛ: App.axaml
--------------------------------------------------------------------------------
x:Class="AvaloniaApplication1.App"
RequestedThemeVariant="Default">
<Application.Resources>
<Color x:Key="PrimaryBackgroundColor">#FFFFFF</Color>
<Color x:Key="SecondaryBackgroundColor">#7FFF00</Color>
<Color x:Key="AccentColor">#00FA9A</Color>
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="{DynamicResource PrimaryBackgroundColor}" />
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{DynamicResource SecondaryBackgroundColor}" />
<SolidColorBrush x:Key="AccentBrush" Color="{DynamicResource AccentColor}" />
</Application.Resources>
<Application.Styles>
<FluentTheme />
<Style Selector="Window">
<Setter Property="Background" Value="{DynamicResource PrimaryBackgroundBrush}" />
</Style>
<Style Selector="TextBox">
<Setter Property="Background" Value="{DynamicResource PrimaryBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" />
</Style>
<Style Selector="Button">
<Setter Property="Background" Value="{DynamicResource SecondaryBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="Border.card">
<Setter Property="Background" Value="{DynamicResource PrimaryBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" />
</Style>
</Application.Styles>
</Application>
--------------------------------------------------------------------------------
ФАЙЛ: App.axaml.cs
--------------------------------------------------------------------------------
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using AvaloniaApplication1.Views;
namespace AvaloniaApplication1;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new LoginWindow();
base.OnFrameworkInitializationCompleted();
}
}
--------------------------------------------------------------------------------
ФАЙЛ: Models/User.cs
--------------------------------------------------------------------------------
namespace AvaloniaApplication1.Models;
public sealed class User
{
public int Id { get; set; }
public string Login { get; set; } = "";
public string Password { get; set; } = "";
public string Role { get; set; } = "";
public string FullName { get; set; } = "";
}
--------------------------------------------------------------------------------
ФАЙЛ: Models/Product.cs
--------------------------------------------------------------------------------
using System;
using System.IO;
namespace AvaloniaApplication1.Models;
public sealed class Product
{
private static readonly string DefaultPhotoPath;
static Product()
{
// Find default photo in Assets
var baseDir = AppContext.BaseDirectory;
var candidates = new[]
{
Path.Combine(baseDir, "Assets", "Icon.png"),
Path.Combine(baseDir, "Assets", "Icon.JPG"),
Path.Combine(baseDir, "Assets", "Icon.ico"),
Path.Combine(baseDir, "..", "..", "..", "Assets", "Icon.png"),
Path.Combine(baseDir, "..", "..", "..", "Assets", "Icon.JPG"),
Path.Combine(Environment.CurrentDirectory, "Assets", "Icon.png"),
Path.Combine(Environment.CurrentDirectory, "Assets", "Icon.JPG"),
};
DefaultPhotoPath = string.Empty;
foreach (var candidate in candidates)
{
var fullPath = Path.GetFullPath(candidate);
if (File.Exists(fullPath))
{
DefaultPhotoPath = fullPath;
break;
}
}
}
public int ProductId { get; set; }
public string Article { get; set; } = "";
public int CategoryId { get; set; }
public int SupplierId { get; set; }
public int TypeProductId { get; set; }
public decimal Price { get; set; }
public string Discount { get; set; } = "0";
public string Description { get; set; } = "";
private string _photo = "";
public string Photo
{
get => string.IsNullOrWhiteSpace(_photo) ? DefaultPhotoPath : _photo;
set => _photo = value ?? "";
}
public decimal FinalPrice
{
get
{
if (!decimal.TryParse(Discount.Replace("%", ""), out var discountValue))
{
discountValue = 0;
}
return Price * (100m - discountValue) / 100m;
}
}
}
--------------------------------------------------------------------------------
ФАЙЛ: Models/Order.cs
--------------------------------------------------------------------------------
namespace AvaloniaApplication1.Models;
public sealed class Order
{
public int OrderId { get; set; }
public string OrderArticle { get; set; } = "";
public string DateOrder { get; set; } = "";
public string DateDelivery { get; set; } = "";
public int PickUpPointId { get; set; }
public string Code { get; set; } = "";
public string StatusOrder { get; set; } = "Новый";
public int UserId { get; set; }
public decimal TotalSum { get; set; }
}
--------------------------------------------------------------------------------
ФАЙЛ: services/DbService.cs
--------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AvaloniaApplication1.Models;
using Npgsql;
namespace AvaloniaApplication1.Services;
public sealed class DbService
{
// Меняешь тут: свою БД, логин, пароль
private readonly string _connectionString = "Host=localhost;Port=5432;Username=postgres;Password=123;Database=Botinki";
// ========== АВТОРИЗАЦИЯ ==========
public User? Login(string login, string password)
{
using var conn = OpenConnection();
using var cmd = new NpgsqlCommand(
"""
SELECT u.user_id, u.login, u.password, COALESCE(r.name_role, 'Клиент') AS role_name,
TRIM(COALESCE(u.name, '') || ' ' || COALESCE(u.second_name, '') || ' ' || COALESCE(u.patronymic, '')) AS full_name
FROM users u
LEFT JOIN roles r ON r.role_id = u.role_id
WHERE u.login = @l AND u.password = @p
""", conn);
cmd.Parameters.AddWithValue("l", login);
cmd.Parameters.AddWithValue("p", password);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
return new User
{
Id = reader.GetInt32(0),
Login = reader.GetString(1),
Password = reader.GetString(2),
Role = NormalizeRole(reader.GetString(3)),
FullName = reader.GetString(4)
};
}
return null;
}
// ========== ТОВАРЫ ==========
public List<Product> GetProducts()
{
var list = new List<Product>();
using var conn = OpenConnection();
using var cmd = new NpgsqlCommand(
"""
SELECT product_id, article, category_id, supplier_id, price, discount, description, photo
FROM products
ORDER BY product_id
""", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
list.Add(new Product
{
ProductId = reader.GetInt32(0),
Article = reader.GetString(1),
CategoryId = reader.IsDBNull(2) ? 0 : reader.GetInt32(2),
SupplierId = reader.IsDBNull(3) ? 0 : reader.GetInt32(3),
TypeProductId = 0,
Price = reader.IsDBNull(4) ? 0m : reader.GetDecimal(4),
Discount = reader.IsDBNull(5) ? "0" : reader.GetString(5),
Description = reader.IsDBNull(6) ? "" : reader.GetString(6),
Photo = reader.IsDBNull(7) ? "" : ResolvePhotoPath(reader.GetString(7))
});
}
return list;
}
// ========== ПОИСК ФОТО ==========
private static string ResolvePhotoPath(string photoValue)
{
if (string.IsNullOrWhiteSpace(photoValue)) return "";
var value = photoValue.Trim();
if (Path.IsPathRooted(value) && File.Exists(value))
return value;
var fileName = Path.GetFileNameWithoutExtension(value);
var directories = new[]
{
Path.Combine(AppContext.BaseDirectory, "Assets"),
Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "Assets")),
Path.Combine(Environment.CurrentDirectory, "Assets")
};
var extensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".bmp" };
foreach (var dir in directories)
{
var exact = Path.Combine(dir, value);
if (File.Exists(exact)) return exact;
foreach (var ext in extensions)
{
var candidate = Path.Combine(dir, fileName + ext);
if (File.Exists(candidate)) return candidate;
}
}
return "";
}
// ========== ПОДКЛЮЧЕНИЕ ==========
private NpgsqlConnection OpenConnection()
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
// ========== НОРМАЛИЗАЦИЯ РОЛИ ==========
private static string NormalizeRole(string role)
{
var r = role.Trim().ToLowerInvariant();
return r switch
{
"администратор" => "admin",
"админ" => "admin",
"менеджер" => "manager",
"manager" => "manager",
"клиент" => "client",
_ => "client"
};
}
}
--------------------------------------------------------------------------------
ФАЙЛ: Views/LoginWindow.axaml
--------------------------------------------------------------------------------
x:Class="AvaloniaApplication1.Views.LoginWindow"
Width="420" Height="380"
Icon="/Assets/Icon.ico"
Title="Авторизация"
WindowStartupLocation="CenterScreen">
<Grid>
<Border Background="{DynamicResource PrimaryBackgroundBrush}" CornerRadius="12" Margin="20">
<StackPanel Margin="25" Spacing="14" VerticalAlignment="Center">
<!-- Logo -->
<Border Background="{DynamicResource SecondaryBackgroundBrush}" CornerRadius="8" Padding="12" HorizontalAlignment="Center">
<Image Source="/Assets/Icon.JPG" Width="64" Height="54" Stretch="Uniform"/>
</Border>
<!-- Title -->
<TextBlock Text="Вход в систему"
FontSize="22"
FontWeight="Bold"
HorizontalAlignment="Center"
Foreground="{DynamicResource AccentBrush}"/>
<!-- Login -->
<TextBox x:Name="loginBox"
Watermark="Логин"
Height="36"
CornerRadius="6"/>
<!-- Password -->
<TextBox x:Name="passwordBox"
PasswordChar="*"
Watermark="Пароль"
Height="36"
CornerRadius="6"/>
<!-- Error -->
<TextBlock x:Name="errorText"
Foreground="#FF4444"
TextWrapping="Wrap"
FontSize="13"
HorizontalAlignment="Center"/>
<!-- Login Button -->
<Button Content="Войти"
Click="Login_Click"
HorizontalAlignment="Stretch"
Height="38"
CornerRadius="6"
FontSize="15"
FontWeight="Bold"
IsDefault="True"/>
<!-- Guest Button -->
<Button Content="Войти как гость"
Click="Guest_Click"
HorizontalAlignment="Stretch"
Height="36"
CornerRadius="6"
FontSize="13"/>
</StackPanel>
</Border>
</Grid>
</Window>
--------------------------------------------------------------------------------
ФАЙЛ: Views/LoginWindow.axaml.cs
--------------------------------------------------------------------------------
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using AvaloniaApplication1.Models;
using AvaloniaApplication1.Services;
namespace AvaloniaApplication1.Views;
public partial class LoginWindow : Window
{
private readonly DbService _dbService = new();
public LoginWindow()
{
InitializeComponent();
}
private void Login_Click(object? sender, RoutedEventArgs e)
{
errorText.Text = string.Empty;
var login = loginBox.Text?.Trim() ?? string.Empty;
var password = passwordBox.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password))
{
errorText.Text = "Введите логин и пароль.";
return;
}
try
{
var user = _dbService.Login(login, password);
if (user is null)
{
errorText.Text = "Неверный логин или пароль.";
return;
}
OpenRoleMenu(user);
}
catch (Exception ex)
{
errorText.Text = $"Ошибка подключения к БД: {ex.Message}";
}
}
private void Guest_Click(object? sender, RoutedEventArgs e)
{
errorText.Text = string.Empty;
OpenRoleMenu(new User
{
Login = "guest",
FullName = "Гость",
Role = "client"
});
}
private void OpenRoleMenu(User user)
{
var roleWindow = new RoleWindow(user, _dbService);
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = roleWindow;
}
roleWindow.Show();
Close();
}
}
--------------------------------------------------------------------------------
ФАЙЛ: Views/RoleWindow.axaml
--------------------------------------------------------------------------------
x:Class="AvaloniaApplication1.Views.RoleWindow"
Width="420" Height="280"
Icon="/Assets/Icon.ico"
Title="Главное меню"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="25" Spacing="14" VerticalAlignment="Center">
<!-- Приветствие -->
<TextBlock x:Name="helloText"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center"/>
<!-- Твоя роль -->
<TextBlock x:Name="roleText"
FontSize="14"
Foreground="#666"
HorizontalAlignment="Center"/>
<!-- Просмотр товаров -->
<Button Content="Просмотр товаров"
Click="OpenProducts_Click"
HorizontalAlignment="Stretch"
Height="40"
FontSize="15"/>
<!-- Выход -->
<Button Content="Выход"
Click="Exit_Click"
HorizontalAlignment="Stretch"
Height="36"
FontSize="13"/>
</StackPanel>
</Window>
--------------------------------------------------------------------------------
ФАЙЛ: Views/RoleWindow.axaml.cs
--------------------------------------------------------------------------------
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using AvaloniaApplication1.Models;
using AvaloniaApplication1.Services;
namespace AvaloniaApplication1.Views;
public partial class RoleWindow : Window
{
private readonly User _user;
private readonly DbService _dbService;
public RoleWindow(User user, DbService dbService)
{
_user = user;
_dbService = dbService;
InitializeComponent();
string name = _user.FullName.Trim();
if (name == "") name = _user.Login;
helloText.Text = $"Привет, {name}!";
roleText.Text = $"Роль: {RoleName(_user.Role)}";
}
private async void OpenProducts_Click(object? sender, RoutedEventArgs e)
{
await new ProductsWindow(_user, _dbService).ShowDialog(this);
}
private void Exit_Click(object? sender, RoutedEventArgs e)
{
var login = new LoginWindow();
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = login;
login.Show();
Close();
}
private static string RoleName(string role) => role switch
{
"admin" => "Администратор",
"manager" => "Менеджер",
_ => "Клиент"
};
}
--------------------------------------------------------------------------------
ФАЙЛ: Views/ProductsWindow.axaml
--------------------------------------------------------------------------------
x:Class="AvaloniaApplication1.Views.ProductsWindow"
Width="950" Height="650"
Icon="/Assets/Icon.ico"
Title="Товары"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
<!-- Шапка -->
<Border Background="{DynamicResource SecondaryBackgroundBrush}" CornerRadius="8" Padding="12" Margin="0,0,0,10">
<TextBlock x:Name="titleText" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center"/>
</Border>
<!-- Карточки товаров -->
<ScrollViewer Grid.Row="1">
<ItemsControl x:Name="productsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{DynamicResource PrimaryBackgroundBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="1"
CornerRadius="8"
Width="200"
Margin="6"
Padding="8">
<StackPanel Spacing="4">
<!-- Фото -->
<Border CornerRadius="6" Background="#F0F0F0" Height="120" HorizontalAlignment="Center" Width="120">
<Image Source="{Binding Photo}" Width="110" Height="110" Stretch="Uniform"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<!-- Артикул -->
<TextBlock Text="{Binding Article, StringFormat='Артикул: {0}'}" FontWeight="Bold" FontSize="12"/>
<!-- Описание -->
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Foreground="#666" MaxHeight="40"/>
<Separator/>
<!-- Цена -->
<TextBlock Text="{Binding Price, StringFormat='Цена: {0:F2} ₽'}" FontSize="12"/>
<!-- Скидка -->
<TextBlock Text="{Binding Discount, StringFormat='Скидка: {0}%'}" Foreground="#FF4444" FontWeight="Bold" FontSize="11"/>
<!-- Итог -->
<TextBlock Text="{Binding FinalPrice, StringFormat='Итог: {0:F2} ₽'}" FontWeight="Bold" FontSize="13"
Foreground="{DynamicResource AccentBrush}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- Кнопка назад -->
<Button Grid.Row="2" Content="Назад" Click="Back_Click" HorizontalAlignment="Left" Margin="0,10,0,0"/>
</Grid>
</Window>
--------------------------------------------------------------------------------
ФАЙЛ: Views/ProductsWindow.axaml.cs
--------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Interactivity;
using AvaloniaApplication1.Models;
using AvaloniaApplication1.Services;
namespace AvaloniaApplication1.Views;
public partial class ProductsWindow : Window
{
private readonly User _user;
private readonly DbService _dbService;
public ProductsWindow(User user, DbService dbService)
{
_user = user;
_dbService = dbService;
InitializeComponent();
titleText.Text = $"Товары ({RoleTitle(_user.Role)})";
LoadProducts();
}
private void LoadProducts()
{
try
{
var products = _dbService.GetProducts();
productsControl.ItemsSource = products;
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
private void Back_Click(object? sender, RoutedEventArgs e)
{
Close();
}
private async void ShowError(string message)
{
var dialog = new Window
{
Width = 400, Height = 150, Title = "Ошибка",
Content = new StackPanel
{
Margin = new Avalonia.Thickness(16),
Spacing = 12,
Children =
{
new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap },
new Button { Content = "OK", HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right }
}
}
};
((Button)((StackPanel)dialog.Content).Children[1]).Click += (_, _) => dialog.Close();
await dialog.ShowDialog(this);
}
private static string RoleTitle(string role) => role switch
{
"admin" => "админ",
"manager" => "менеджер",
_ => "клиент"
};
}
================================================================================
КОНЕЦ ВСЕХ ФАЙЛОВ
================================================================================