feat(pos-ui): вёрстка кассы 1:1 по эталон-макету
Перенёс макет Mac-Claude (docs/pos-design/pos-mockup.html) в Avalonia по размерам, а не на глаз: - Оплата: модалка 880px поверх затемнённого экрана продажи (как в макете); сегменты Нал/Карта/Смешанная с векторными иконками (активный зелёный — иконка белая через стиль Button.seg.on Path); быстрые суммы 3×2 (без сдачи/4000/5000/10000/20000/своя сумма); клавиатура 3×4 по 72px. - Продажа: исправлена подсветка выбранной строки — локальные Background/IsVisible перекрывали сеттеры стиля :selected; теперь зелёный акцент слева + светло-зелёный фон работают. Бейдж «Отложенные · N». Чип связи «На связи · очередь пуста». - Демо: --demo-pay открывает модалку оплаты для скриншота на Windows; PayScrim в скриншотере рендерит оплату поверх реальной оболочки продажи. - Инструмент сверки: CompareRunner (--compare, SkiaSharp) складывает «макет | касса» рядом → docs/pos-design/compare-*.png. Скорость не ухудшена (старт 74мс, скан 13.7мс). --uitest OK, 82 unit зелёные. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
85e486abc7
commit
6ae79e37fb
|
|
@ -20,6 +20,8 @@ public sealed class PosProduct
|
|||
public bool IsArchived { get; init; }
|
||||
public bool IsFavorite { get; init; }
|
||||
public bool IsWeighed => Packaging == PosPackaging.Weight;
|
||||
/// <summary>Цена для плитки избранного: «690 ₸/кг» для весового, «280 ₸» для штучного.</summary>
|
||||
public string PriceText => IsWeighed ? $"{Price:N0} ₸/{UnitCode}" : $"{Price:N0} ₸";
|
||||
}
|
||||
|
||||
/// <summary>Строка чека. Количество дробное (весовой/разлив). Скидка — абсолютная
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ public partial class App : Application
|
|||
/// сервера — для скриншота реального окна на Windows.</summary>
|
||||
public static bool DemoMode { get; set; }
|
||||
|
||||
/// <summary>Демо с открытой модалкой оплаты (--demo-pay) — для скриншота оплаты на Windows.</summary>
|
||||
public static bool DemoPay { get; set; }
|
||||
|
||||
public override void Initialize() => AvaloniaXamlLoader.Load(this);
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
|
|
@ -23,9 +26,15 @@ public override void OnFrameworkInitializationCompleted()
|
|||
{
|
||||
// Демо: окно с примерным чеком, без AppServices/логина; развёрнуто
|
||||
// на весь экран — для скриншота реального окна на Windows.
|
||||
var demoVm = new MainWindowViewModel();
|
||||
if (DemoPay)
|
||||
{
|
||||
demoVm.Payment = PaymentViewModel.SampleCash();
|
||||
demoVm.IsPaymentOpen = true;
|
||||
}
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
DataContext = demoVm,
|
||||
WindowState = Avalonia.Controls.WindowState.Maximized,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ public static int Main(string[] args)
|
|||
// --uitest : headless UI-автотест экрана продажи (Avalonia.Headless, для CI/xvfb).
|
||||
if (args.Contains("--smoke")) return SmokeRunner.Run(args);
|
||||
if (args.Contains("--screenshots")) return ScreenshotRunner.Run(args);
|
||||
if (args.Contains("--compare")) return CompareRunner.Run(args);
|
||||
if (args.Contains("--uitest")) return UiTestRunner.Run(args);
|
||||
|
||||
// --demo : показать реальное окно с примерами данных (для скриншота на Windows).
|
||||
if (args.Contains("--demo")) App.DemoMode = true;
|
||||
if (args.Contains("--demo") || args.Contains("--demo-pay")) App.DemoMode = true;
|
||||
if (args.Contains("--demo-pay")) App.DemoPay = true;
|
||||
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -16,20 +16,21 @@
|
|||
<Setter Property="Padding" Value="12,6"/>
|
||||
<Setter Property="Background" Value="{DynamicResource PosSubtle}"/>
|
||||
</Style>
|
||||
<Style Selector="Border.chip > TextBlock">
|
||||
<Style Selector="Border.chip TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource PosTextMuted}"/>
|
||||
<Setter Property="FontSize" Value="13.5"/>
|
||||
<Setter Property="FontWeight" Value="Medium"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Border.chip.ok">
|
||||
<Setter Property="Background" Value="{DynamicResource PosBrandSoft}"/>
|
||||
</Style>
|
||||
<Style Selector="Border.chip.ok > TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosBrand}"/></Style>
|
||||
<Style Selector="Border.chip.ok TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosBrandDark}"/></Style>
|
||||
<Style Selector="Border.chip.net"><Setter Property="Background" Value="{DynamicResource PosChipNet}"/></Style>
|
||||
<Style Selector="Border.chip.warn">
|
||||
<Setter Property="Background" Value="{DynamicResource PosWarnSoft}"/>
|
||||
</Style>
|
||||
<Style Selector="Border.chip.warn > TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosWarn}"/></Style>
|
||||
<Style Selector="Border.chip.warn TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosWarn}"/></Style>
|
||||
|
||||
<!-- ── Кнопки ───────────────────────────────────────────────────── -->
|
||||
<Style Selector="Button.pos">
|
||||
|
|
@ -86,8 +87,9 @@
|
|||
<Style Selector="Button.danger">
|
||||
<Setter Property="Background" Value="{DynamicResource PosCard}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PosDanger}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource PosDanger}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource PosDangerBorder}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.danger TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosDanger}"/></Style>
|
||||
<Style Selector="Button.danger:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource PosDangerSoft}"/>
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource PosDanger}"/>
|
||||
|
|
@ -136,6 +138,8 @@
|
|||
<Style Selector="Button.seg.on /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource PosBrand}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.seg Path"><Setter Property="Stroke" Value="{DynamicResource PosTextMuted}"/></Style>
|
||||
<Style Selector="Button.seg.on Path"><Setter Property="Stroke" Value="{DynamicResource PosBrandText}"/></Style>
|
||||
|
||||
<!-- ── Поля ввода ──────────────────────────────────────────────── -->
|
||||
<Style Selector="TextBox.pos">
|
||||
|
|
@ -165,4 +169,45 @@
|
|||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Padding" Value="9,4"/>
|
||||
</Style>
|
||||
|
||||
<!-- kbd-капсула (как <kbd> в макете) -->
|
||||
<Style Selector="Border.kbd">
|
||||
<Setter Property="MinWidth" Value="30"/>
|
||||
<Setter Property="Height" Value="24"/>
|
||||
<Setter Property="Padding" Value="7,0"/>
|
||||
<Setter Property="CornerRadius" Value="6"/>
|
||||
<Setter Property="Background" Value="{DynamicResource PosCard}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource PosBorder}"/>
|
||||
<Setter Property="BorderThickness" Value="1,1,1,2"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Border.kbd > TextBlock">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PosTextMuted}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Border.kbd.onbrand"><Setter Property="Background" Value="#33FFFFFF"/><Setter Property="BorderThickness" Value="0"/></Style>
|
||||
<Style Selector="Border.kbd.onbrand > TextBlock"><Setter Property="Foreground" Value="White"/></Style>
|
||||
|
||||
<!-- Грань степпера (− / +) — слитный блок -->
|
||||
<Style Selector="Button.stepbtn">
|
||||
<Setter Property="Background" Value="{DynamicResource PosStepBg}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PosText}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="FontSize" Value="22"/>
|
||||
<Setter Property="FontWeight" Value="Medium"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Button.stepbtn /template/ ContentPresenter">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Button.stepbtn:pointerover /template/ ContentPresenter"><Setter Property="Background" Value="{DynamicResource PosSubtle}"/></Style>
|
||||
</Styles>
|
||||
|
|
|
|||
|
|
@ -7,19 +7,25 @@
|
|||
<SolidColorBrush x:Key="PosBg" Color="#F4F6F5"/>
|
||||
<SolidColorBrush x:Key="PosCard" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="PosSubtle" Color="#F0F3F1"/>
|
||||
<SolidColorBrush x:Key="PosChipNet" Color="#EEF2F1"/>
|
||||
<SolidColorBrush x:Key="PosStepBg" Color="#F7F9F8"/>
|
||||
<SolidColorBrush x:Key="PosBorder" Color="#E3E8E6"/>
|
||||
<SolidColorBrush x:Key="PosText" Color="#0F1F17"/>
|
||||
<SolidColorBrush x:Key="PosTextMuted" Color="#5F6F68"/>
|
||||
<SolidColorBrush x:Key="PosPlaceholder" Color="#8A9993"/>
|
||||
<SolidColorBrush x:Key="PosBrand" Color="#16A34A"/>
|
||||
<SolidColorBrush x:Key="PosBrandDark" Color="#12863D"/>
|
||||
<SolidColorBrush x:Key="PosBrandHover" Color="#15803D"/>
|
||||
<SolidColorBrush x:Key="PosBrandPressed" Color="#166534"/>
|
||||
<SolidColorBrush x:Key="PosBrandText" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="PosBrandSoft" Color="#DCFCE7"/>
|
||||
<SolidColorBrush x:Key="PosBrandSoft" Color="#E8F7EE"/>
|
||||
<SolidColorBrush x:Key="PosSelRow" Color="#F7FBF8"/>
|
||||
<SolidColorBrush x:Key="PosDanger" Color="#DC2626"/>
|
||||
<SolidColorBrush x:Key="PosDangerSoft" Color="#FEE2E2"/>
|
||||
<SolidColorBrush x:Key="PosDangerBorder" Color="#F3C4C4"/>
|
||||
<SolidColorBrush x:Key="PosWarn" Color="#B45309"/>
|
||||
<SolidColorBrush x:Key="PosWarnSoft" Color="#FEF3C7"/>
|
||||
<SolidColorBrush x:Key="PosScrim" Color="#33203028"/>
|
||||
<SolidColorBrush x:Key="PosScrim" Color="#6B0F1F17"/>
|
||||
</ResourceDictionary>
|
||||
|
||||
<!-- ── Тёмная тема (опция, те же токены) ───────────────────────── -->
|
||||
|
|
@ -27,19 +33,25 @@
|
|||
<SolidColorBrush x:Key="PosBg" Color="#0F1713"/>
|
||||
<SolidColorBrush x:Key="PosCard" Color="#15201B"/>
|
||||
<SolidColorBrush x:Key="PosSubtle" Color="#1B2821"/>
|
||||
<SolidColorBrush x:Key="PosChipNet" Color="#1B2821"/>
|
||||
<SolidColorBrush x:Key="PosStepBg" Color="#1B2821"/>
|
||||
<SolidColorBrush x:Key="PosBorder" Color="#2A3A31"/>
|
||||
<SolidColorBrush x:Key="PosText" Color="#E8EFEA"/>
|
||||
<SolidColorBrush x:Key="PosTextMuted" Color="#9BAAA2"/>
|
||||
<SolidColorBrush x:Key="PosPlaceholder" Color="#7E8E86"/>
|
||||
<SolidColorBrush x:Key="PosBrand" Color="#22C55E"/>
|
||||
<SolidColorBrush x:Key="PosBrandDark" Color="#34D37F"/>
|
||||
<SolidColorBrush x:Key="PosBrandHover" Color="#16A34A"/>
|
||||
<SolidColorBrush x:Key="PosBrandPressed" Color="#15803D"/>
|
||||
<SolidColorBrush x:Key="PosBrandText" Color="#06140C"/>
|
||||
<SolidColorBrush x:Key="PosBrandSoft" Color="#13331F"/>
|
||||
<SolidColorBrush x:Key="PosSelRow" Color="#132A1D"/>
|
||||
<SolidColorBrush x:Key="PosDanger" Color="#F87171"/>
|
||||
<SolidColorBrush x:Key="PosDangerSoft" Color="#3A1A1A"/>
|
||||
<SolidColorBrush x:Key="PosDangerBorder" Color="#5A2A2A"/>
|
||||
<SolidColorBrush x:Key="PosWarn" Color="#FBBF24"/>
|
||||
<SolidColorBrush x:Key="PosWarnSoft" Color="#33280D"/>
|
||||
<SolidColorBrush x:Key="PosScrim" Color="#66000000"/>
|
||||
<SolidColorBrush x:Key="PosScrim" Color="#6B000000"/>
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
|
|
|||
72
src/food-market.pos/Tooling/CompareRunner.cs
Normal file
72
src/food-market.pos/Tooling/CompareRunner.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace foodmarket.Pos.Tooling;
|
||||
|
||||
/// <summary>Складывает «макет | касса» рядом в одно изображение для поэлементной
|
||||
/// сверки вёрстки. Использует уже отрисованные PNG: docs/pos-design/mockup-*.png
|
||||
/// (эталон) и docs/pos-screenshots/*.png (наш рендер).
|
||||
/// Запуск: dotnet run -- --compare</summary>
|
||||
public static class CompareRunner
|
||||
{
|
||||
private sealed record Pair(string Name, string Mockup, string Shot);
|
||||
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
var root = FindRepoRoot();
|
||||
var design = Path.Combine(root, "docs", "pos-design");
|
||||
var shots = Path.Combine(root, "docs", "pos-screenshots");
|
||||
Directory.CreateDirectory(design);
|
||||
|
||||
var pairs = new[]
|
||||
{
|
||||
new Pair("compare-sale", Path.Combine(design, "mockup-sale-1920x1080.png"), Path.Combine(shots, "04-sale-items-1920x1080.png")),
|
||||
new Pair("compare-payment", Path.Combine(design, "mockup-payment-1920x1080.png"), Path.Combine(shots, "05-payment-cash-1920x1080.png")),
|
||||
};
|
||||
|
||||
foreach (var p in pairs)
|
||||
{
|
||||
if (!File.Exists(p.Mockup)) { Console.WriteLine($"[skip] нет макета {p.Mockup}"); continue; }
|
||||
if (!File.Exists(p.Shot)) { Console.WriteLine($"[skip] нет рендера {p.Shot}"); continue; }
|
||||
|
||||
using var left = SKBitmap.Decode(p.Mockup);
|
||||
using var right = SKBitmap.Decode(p.Shot);
|
||||
|
||||
const int gap = 24;
|
||||
const int header = 56;
|
||||
int h = Math.Max(left.Height, right.Height);
|
||||
int w = left.Width + right.Width + gap;
|
||||
|
||||
var outPath = Path.Combine(design, p.Name + ".png");
|
||||
using var surface = SKSurface.Create(new SKImageInfo(w, h + header));
|
||||
var c = surface.Canvas;
|
||||
c.Clear(new SKColor(0xB4, 0xB4, 0xB4));
|
||||
|
||||
using var font = new SKPaint { Color = SKColors.White, TextSize = 26, IsAntialias = true, FakeBoldText = true };
|
||||
c.DrawText("МАКЕТ (эталон)", 16, 38, font);
|
||||
c.DrawText("КАССА (Avalonia)", left.Width + gap + 16, 38, font);
|
||||
|
||||
c.DrawBitmap(left, 0, header);
|
||||
c.DrawBitmap(right, left.Width + gap, header);
|
||||
|
||||
// вертикальный разделитель
|
||||
using var line = new SKPaint { Color = new SKColor(0x30, 0x30, 0x30), StrokeWidth = 2 };
|
||||
c.DrawLine(left.Width + gap / 2f, header, left.Width + gap / 2f, h + header, line);
|
||||
|
||||
using var img = surface.Snapshot();
|
||||
using var data = img.Encode(SKEncodedImageFormat.Png, 90);
|
||||
using var fs = File.OpenWrite(outPath);
|
||||
data.SaveTo(fs);
|
||||
Console.WriteLine($"[compare] {outPath} ({w}×{h + header})");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string FindRepoRoot()
|
||||
{
|
||||
var dir = AppContext.BaseDirectory;
|
||||
while (dir is not null && !Directory.Exists(Path.Combine(dir, ".git")))
|
||||
dir = Directory.GetParent(dir)?.FullName;
|
||||
return dir ?? Directory.GetCurrentDirectory();
|
||||
}
|
||||
}
|
||||
|
|
@ -78,18 +78,15 @@ private static Control ShellWith(SaleViewModel sale)
|
|||
vm.CurrentPage = sale;
|
||||
return new MainWindow { DataContext = vm };
|
||||
}
|
||||
/// <summary>Модальная оплата на затемнённой подложке (как в оболочке):
|
||||
/// светлый фон → полупрозрачная тёмная подложка → белое модальное окно.</summary>
|
||||
/// <summary>Модальная оплата поверх затемнённого экрана продажи — как в оболочке
|
||||
/// и как в макете: чек виден под полупрозрачной тёмной подложкой.</summary>
|
||||
private static Control PayScrim(PaymentViewModel pay)
|
||||
=> new Border
|
||||
{
|
||||
Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#F4F6F5")),
|
||||
Child = new Border
|
||||
{
|
||||
Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#660F1F17")),
|
||||
Child = new PaymentView { DataContext = pay },
|
||||
},
|
||||
};
|
||||
var vm = new MainWindowViewModel(); // в CurrentPage уже лежит пример продажи
|
||||
vm.Payment = pay;
|
||||
vm.IsPaymentOpen = true;
|
||||
return new MainWindow { DataContext = vm };
|
||||
}
|
||||
|
||||
private static Control ShellOffline()
|
||||
{
|
||||
|
|
@ -160,7 +157,9 @@ private static void WriteGallery(string dir, List<(string file, string caption)>
|
|||
foreach (var w in wins)
|
||||
{
|
||||
var rel = "windows/" + Path.GetFileName(w);
|
||||
sb.Append($"<figure><a href=\"{rel}\" target=\"_blank\"><img src=\"{rel}\" loading=\"lazy\"></a><figcaption>Windows 11 — экран продажи (реальное окно)</figcaption></figure>");
|
||||
var name = Path.GetFileNameWithoutExtension(w);
|
||||
var label = name.Contains("pay", StringComparison.OrdinalIgnoreCase) ? "экран оплаты" : "экран продажи";
|
||||
sb.Append($"<figure><a href=\"{rel}\" target=\"_blank\"><img src=\"{rel}\" loading=\"lazy\"></a><figcaption>Windows 11 — {label} (реальное окно)</figcaption></figure>");
|
||||
}
|
||||
sb.Append("</div>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||
[ObservableProperty] private string _cashierName = "—";
|
||||
[ObservableProperty] private string _shiftNumber = "смена не открыта";
|
||||
[ObservableProperty] private bool _isShiftOpen;
|
||||
[ObservableProperty] private string _pointName = "Магазин";
|
||||
[ObservableProperty] private string _storeName = "Магазин";
|
||||
[ObservableProperty] private string _pointName = "Касса";
|
||||
[ObservableProperty] private bool _isOnline = true;
|
||||
[ObservableProperty] private int _queueCount;
|
||||
[ObservableProperty] private PaymentViewModel? _payment;
|
||||
|
|
@ -34,14 +35,14 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||
|
||||
public string NetBadge => IsOnline ? "На связи" : "Нет связи";
|
||||
public string ShiftBadge => IsShiftOpen ? $"Смена {ShiftNumber}" : "Смена закрыта";
|
||||
public string QueueBadge => QueueCount > 0 ? $"· {QueueCount} в очереди" : "";
|
||||
public string QueueBadge => QueueCount > 0 ? $"· {QueueCount} в очереди" : "· очередь пуста";
|
||||
partial void OnIsOnlineChanged(bool value) { OnPropertyChanged(nameof(NetBadge)); }
|
||||
partial void OnQueueCountChanged(int value) { OnPropertyChanged(nameof(QueueBadge)); }
|
||||
partial void OnIsShiftOpenChanged(bool value) => OnPropertyChanged(nameof(ShiftBadge));
|
||||
partial void OnShiftNumberChanged(string value) => OnPropertyChanged(nameof(ShiftBadge));
|
||||
|
||||
/// <summary>Конструктор для дизайнера/скриншотов (демо-данные).</summary>
|
||||
public MainWindowViewModel() { CurrentPage = SaleViewModel.Sample(); CashierName = "Пётр Кассиров"; ShiftNumber = "С-2026-000001"; IsShiftOpen = true; PointName = "Магазин №1 · ул. Абая 10"; }
|
||||
public MainWindowViewModel() { CurrentPage = SaleViewModel.Sample(); CashierName = "Пётр Кассиров"; ShiftNumber = "С-2026-000012 открыта с 09:00"; IsShiftOpen = true; StoreName = "Демо-магазин"; PointName = "Касса 1"; }
|
||||
|
||||
public MainWindowViewModel(AppServices svc) { _svc = svc; if (svc.Session is not null) StartSession(); else ShowLogin(); }
|
||||
|
||||
|
|
@ -363,9 +364,10 @@ public string QtyText
|
|||
}
|
||||
}
|
||||
public bool HasDiscount => Discount > 0m;
|
||||
/// <summary>Пояснение скидки в строке, чтобы цена×кол-во ≠ сумма не выглядело
|
||||
/// ошибкой: «1 560 → 1 500 ₸, скидка 60».</summary>
|
||||
public string DiscountText => $"{Gross:N0} → {LineTotal:N0} ₸, скидка {Discount:N0}";
|
||||
/// <summary>Подпись цены за единицу: «280 ₸ за шт», «690 ₸ за кг».</summary>
|
||||
public string UnitPriceText => $"{Price:N0} ₸ за {Unit}";
|
||||
/// <summary>Строка со скидкой: «520 ₸ за шт · скидка 60 ₸ (1 560 → 1 500 ₸)».</summary>
|
||||
public string DiscountText => $"{Price:N0} ₸ за {Unit} · скидка {Discount:N0} ₸ ({Gross:N0} → {LineTotal:N0} ₸)";
|
||||
partial void OnQuantityChanged(decimal value)
|
||||
{ OnPropertyChanged(nameof(LineTotal)); OnPropertyChanged(nameof(QtyText)); OnPropertyChanged(nameof(Gross)); OnPropertyChanged(nameof(DiscountText)); }
|
||||
partial void OnDiscountChanged(decimal value)
|
||||
|
|
@ -396,6 +398,10 @@ public partial class SaleViewModel : ViewModelBase
|
|||
[ObservableProperty] private SaleLineVm? _selectedLine;
|
||||
public bool IsCartEmpty => ItemCount == 0;
|
||||
public bool HasAnyDiscount => Discount > 0m;
|
||||
// Блок «Последний чек / Чеков за смену» (информационный, заполняет панель).
|
||||
[ObservableProperty] private string _lastReceiptNumber = "—";
|
||||
[ObservableProperty] private string _lastReceiptInfo = "—";
|
||||
[ObservableProperty] private int _receiptsInShift;
|
||||
partial void OnItemCountChanged(int value) => OnPropertyChanged(nameof(IsCartEmpty));
|
||||
partial void OnDiscountChanged(decimal value) => OnPropertyChanged(nameof(HasAnyDiscount));
|
||||
|
||||
|
|
@ -520,7 +526,9 @@ public async Task CommitPaymentAsync(decimal cash, decimal card, decimal change)
|
|||
|
||||
[RelayCommand] private void Clear() { _cart.Clear(); Lines.Clear(); SelectedLine = null; Recalc(); Message = "Чек очищен"; }
|
||||
|
||||
public int ParkedCount => _parked.Count;
|
||||
private int _demoParked; // только для скриншотов/демо (Sample)
|
||||
public int ParkedCount => _parked.Count + _demoParked;
|
||||
public bool HasParked => ParkedCount > 0;
|
||||
|
||||
/// <summary>Отложить текущий чек и начать новый (обслужить другого покупателя).</summary>
|
||||
[RelayCommand]
|
||||
|
|
@ -567,12 +575,15 @@ public static SaleViewModel Sample()
|
|||
vm.Total = decimal.Round(vm.Subtotal - vm.Discount, 2);
|
||||
vm.ItemCount = vm.Lines.Count;
|
||||
vm.Message = "Чек готов к оплате — F12 Оплата";
|
||||
vm.SelectedLine = vm.Lines[3]; // выбранная строка (Сахар) — зелёный акцент
|
||||
vm.LastReceiptNumber = "№ 000148"; vm.LastReceiptInfo = "1 250 ₸ · нал"; vm.ReceiptsInShift = 47;
|
||||
vm._demoParked = 2; // в демо — 2 отложенных чека (бейдж на кнопке «Отложенные»)
|
||||
PosProduct Fav(string n, string unit, decimal price, bool weighed) => new()
|
||||
{ Id = Guid.NewGuid(), Name = n, Barcodes = Array.Empty<string>(), UnitCode = unit, Packaging = weighed ? PosPackaging.Weight : PosPackaging.Piece, VatPercent = 12m, VatEnabled = true, Price = price, IsFavorite = true };
|
||||
foreach (var f in new[] {
|
||||
Fav("Хлеб «Семейный»", "шт", 280m, false), Fav("Молоко 2.5%", "шт", 450m, false),
|
||||
Fav("Хлеб «Семейный»", "шт", 280m, false), Fav("Молоко 2,5%", "шт", 450m, false),
|
||||
Fav("Яблоки (вес)", "кг", 690m, true), Fav("Картофель (вес)", "кг", 280m, true),
|
||||
Fav("Пакет-майка", "шт", 20m, false), Fav("Вода 0,5л", "шт", 150m, false) })
|
||||
Fav("Пакет-майка", "шт", 20m, false), Fav("Вода 0,5 л", "шт", 150m, false) })
|
||||
vm.Favorites.Add(f);
|
||||
return vm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,63 +7,61 @@
|
|||
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Panel>
|
||||
<Grid RowDefinitions="64,*,46">
|
||||
<Grid RowDefinitions="64,*,40">
|
||||
|
||||
<!-- Верхняя полоса -->
|
||||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="20,0">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
|
||||
<Border Background="{DynamicResource PosBrand}" CornerRadius="9" Width="34" Height="34">
|
||||
<TextBlock Text="FM" Foreground="White" FontWeight="Bold" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="Food Market · Касса" Foreground="{DynamicResource PosText}" FontWeight="Bold" FontSize="16"/>
|
||||
<TextBlock Text="{Binding PointName}" Foreground="{DynamicResource PosTextMuted}" FontSize="12.5"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="24" VerticalAlignment="Center">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="20">
|
||||
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||
</TextBlock>
|
||||
<Border Width="1" Height="28" Background="{DynamicResource PosBorder}"/>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||||
<Run Text="Магазин "/><Run Text="{Binding StoreName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/><Run Text=" · Точка "/><Run Text="{Binding PointName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||
</TextBlock>
|
||||
<Border Width="1" Height="28" Background="{DynamicResource PosBorder}"/>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||||
<Run Text="Кассир "/><Run Text="{Binding CashierName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||
</TextBlock>
|
||||
<Border Width="1" Height="28" Background="{DynamicResource PosBorder}"
|
||||
IsVisible="{Binding UpdateMessage, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="13" Foreground="{DynamicResource PosWarn}" Text="{Binding UpdateMessage}"
|
||||
IsVisible="{Binding UpdateMessage, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<Border Classes="chip" Classes.ok="{Binding IsShiftOpen}" Classes.warn="{Binding !IsShiftOpen}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||||
<TextBlock Text="{Binding ShiftBadge}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Classes="chip" Classes.ok="{Binding IsOnline}" Classes.warn="{Binding !IsOnline}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes="chip net">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||||
<TextBlock Text="{Binding NetBadge}"/>
|
||||
<TextBlock Text="{Binding QueueBadge}" IsVisible="{Binding QueueBadge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button Classes="pos ghost" Width="42" Height="38" Padding="0" Command="{Binding ToggleThemeCommand}" ToolTip.Tip="Тёмная / светлая тема">
|
||||
<PathIcon Width="18" Height="18" Foreground="{DynamicResource PosTextMuted}"
|
||||
Data="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
|
||||
</Button>
|
||||
<Border Width="1" Height="26" Background="{DynamicResource PosBorder}" Margin="4,0"/>
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Right">
|
||||
<TextBlock Text="{Binding CashierName}" Foreground="{DynamicResource PosText}" FontSize="14" FontWeight="SemiBold" HorizontalAlignment="Right"/>
|
||||
<TextBlock Text="кассир" Foreground="{DynamicResource PosTextMuted}" FontSize="12" HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="1" Text="{Binding UpdateMessage}" Foreground="{DynamicResource PosWarn}" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="12.5"
|
||||
IsVisible="{Binding UpdateMessage, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Текущая страница -->
|
||||
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}"/>
|
||||
|
||||
<!-- Горячие клавиши -->
|
||||
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0">
|
||||
<ItemsControl VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="8"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F1" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Помощь" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F2" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Поиск" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F7" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Отложить" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F9" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Возврат" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F10" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Смена" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="F12" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosBrand}"/><TextBlock Text="Оплата" FontSize="12" Foreground="{DynamicResource PosBrand}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="Del" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Удалить" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
<Border Classes="hotkey"><StackPanel Orientation="Horizontal" Spacing="6"><TextBlock Text="Esc" FontWeight="Bold" FontSize="11.5" Foreground="{DynamicResource PosTextMuted}"/><TextBlock Text="Отмена" FontSize="12" Foreground="{DynamicResource PosTextMuted}"/></StackPanel></Border>
|
||||
</ItemsControl>
|
||||
<!-- Полоса горячих клавиш -->
|
||||
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="24,0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="28" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F1"/></Border><TextBlock Text="Помощь" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F2"/></Border><TextBlock Text="Поиск" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F4"/></Border><TextBlock Text="Скидка" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F7"/></Border><TextBlock Text="Отложить" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F9"/></Border><TextBlock Text="Возврат" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F10"/></Border><TextBlock Text="Смена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F12"/></Border><TextBlock Text="Оплата" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Del"/></Border><TextBlock Text="Удалить строку" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Esc"/></Border><TextBlock Text="Отмена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,76 +3,105 @@
|
|||
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
||||
x:Class="foodmarket.Pos.Views.PaymentView"
|
||||
x:DataType="vm:PaymentViewModel">
|
||||
<Border Classes="card" Width="720" Padding="28" CornerRadius="18" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel Spacing="16">
|
||||
<Border Width="880" Background="{DynamicResource PosCard}" CornerRadius="20" Padding="32" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
BoxShadow="0 24 64 0 #40000000">
|
||||
<StackPanel Spacing="24">
|
||||
<!-- Заголовок -->
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="Оплата" FontSize="22" FontWeight="Bold" Foreground="{DynamicResource PosText}" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<TextBlock Text="К оплате" Foreground="{DynamicResource PosTextMuted}" FontSize="16" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="30" FontWeight="Bold"/>
|
||||
<TextBlock Text="Оплата" FontSize="24" FontWeight="Bold" Foreground="{DynamicResource PosText}" VerticalAlignment="Bottom"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Bottom">
|
||||
<TextBlock Text="К оплате" Foreground="{DynamicResource PosTextMuted}" FontSize="16" VerticalAlignment="Bottom" Margin="0,0,0,6"/>
|
||||
<TextBlock Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="40" FontWeight="ExtraBold"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Сегменты способа оплаты (слитный переключатель) -->
|
||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="12" Padding="4">
|
||||
<!-- Сегменты способа оплаты (в серой подложке) -->
|
||||
<Border Background="{DynamicResource PosChipNet}" CornerRadius="12" Padding="4">
|
||||
<UniformGrid Columns="3">
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsCash}" Margin="2" Content="Наличные" Command="{Binding SelectCashCommand}"/>
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsCard}" Margin="2" Content="Карта" Command="{Binding SelectCardCommand}"/>
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsMixed}" Margin="2" Content="Смешанная" Command="{Binding SelectMixedCommand}"/>
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsCash}" Margin="2" Command="{Binding SelectCashCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="20" Height="20" Stretch="Uniform" StrokeThickness="2" StrokeJoin="Round" Data="M3 6h18v12H3zM9 12a3 3 0 106 0 3 3 0 10-6 0"/>
|
||||
<TextBlock Text="Наличные" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsCard}" Margin="2" Command="{Binding SelectCardCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="20" Height="20" Stretch="Uniform" StrokeThickness="2" StrokeJoin="Round" Data="M3 6h18v12H3zM3 10h18"/>
|
||||
<TextBlock Text="Карта" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="pos seg" Classes.on="{Binding IsMixed}" Margin="2" Command="{Binding SelectMixedCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="20" Height="20" Stretch="Uniform" StrokeThickness="2" StrokeLineCap="Round" StrokeJoin="Round" Data="M7 7h12l-3-3M17 17H5l3 3"/>
|
||||
<TextBlock Text="Смешанная" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</UniformGrid>
|
||||
</Border>
|
||||
|
||||
<!-- Тело: поле+быстрые суммы | клавиатура -->
|
||||
<Grid ColumnDefinitions="*,300">
|
||||
<!-- Левая часть: суммы -->
|
||||
<StackPanel Grid.Column="0" Spacing="12" Margin="0,0,20,0">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Получено наличными" Foreground="{DynamicResource PosTextMuted}" FontSize="14"/>
|
||||
<TextBox Classes="pos" FontSize="24" Text="{Binding PaidCash}"/>
|
||||
<StackPanel Grid.Column="0" Margin="0,0,24,0">
|
||||
<TextBlock Text="Получено наличными" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||
<Border Height="64" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBrand}" BorderThickness="2" CornerRadius="12" Padding="20,0">
|
||||
<TextBlock Text="{Binding PaidCash, StringFormat='{}{0:N0} ₸'}" FontSize="30" FontWeight="Bold" Foreground="{DynamicResource PosText}" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<StackPanel Spacing="4" Margin="0,10,0,0" IsVisible="{Binding IsMixed}">
|
||||
<TextBlock Text="Картой" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold"/>
|
||||
<TextBox Classes="pos" FontSize="22" Text="{Binding PaidCard}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="4" IsVisible="{Binding IsMixed}">
|
||||
<TextBlock Text="Картой" Foreground="{DynamicResource PosTextMuted}" FontSize="14"/>
|
||||
<TextBox Classes="pos" FontSize="24" Text="{Binding PaidCard}"/>
|
||||
</StackPanel>
|
||||
<!-- Быстрые суммы -->
|
||||
<UniformGrid Columns="3" IsVisible="{Binding !IsCard}">
|
||||
<Button Classes="pos ghost" Margin="3" FontSize="14.5" Padding="4,0" Content="Без сдачи" Command="{Binding QuickCashCommand}" CommandParameter="exact"/>
|
||||
<Button Classes="pos ghost" Margin="3" Content="1 000" Command="{Binding QuickCashCommand}" CommandParameter="1000"/>
|
||||
<Button Classes="pos ghost" Margin="3" Content="2 000" Command="{Binding QuickCashCommand}" CommandParameter="2000"/>
|
||||
<Button Classes="pos ghost" Margin="3" Content="5 000" Command="{Binding QuickCashCommand}" CommandParameter="5000"/>
|
||||
<Button Classes="pos ghost" Margin="3" Content="10 000" Command="{Binding QuickCashCommand}" CommandParameter="10000"/>
|
||||
<Button Classes="pos ghost" Margin="3" Content="20 000" Command="{Binding QuickCashCommand}" CommandParameter="20000"/>
|
||||
|
||||
<!-- Быстрые суммы 3×2 -->
|
||||
<UniformGrid Columns="3" Margin="0,14,0,0" IsVisible="{Binding !IsCard}">
|
||||
<Button Classes="pos" FontSize="16" Height="52" Margin="0,0,5,10" Foreground="{DynamicResource PosBrandDark}" Background="{DynamicResource PosBrandSoft}" BorderBrush="#BFE6CD" Content="Без сдачи" Command="{Binding QuickCashCommand}" CommandParameter="exact"/>
|
||||
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="5,0,5,10" Content="4 000" Command="{Binding QuickCashCommand}" CommandParameter="4000"/>
|
||||
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="5,0,0,10" Content="5 000" Command="{Binding QuickCashCommand}" CommandParameter="5000"/>
|
||||
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="0,0,5,0" Content="10 000" Command="{Binding QuickCashCommand}" CommandParameter="10000"/>
|
||||
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="5,0,5,0" Content="20 000" Command="{Binding QuickCashCommand}" CommandParameter="20000"/>
|
||||
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="5,0,0,0" Content="Своя сумма" Command="{Binding KeyClearCommand}"/>
|
||||
</UniformGrid>
|
||||
|
||||
<!-- Сдача -->
|
||||
<Border Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Padding="16,14">
|
||||
<Border Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Height="84" Margin="0,14,0,0" Padding="22,0">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="СДАЧА" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Change, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="36" FontWeight="Bold"/>
|
||||
<TextBlock Text="СДАЧА" Foreground="{DynamicResource PosBrandDark}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Change, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrandDark}" FontSize="44" FontWeight="ExtraBold" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Правая часть: цифровая клавиатура 3×4 (ровные квадраты) -->
|
||||
<!-- Клавиатура 3×4 одинаковых 72px -->
|
||||
<UniformGrid Grid.Column="1" Columns="3" Rows="4" VerticalAlignment="Top" IsVisible="{Binding !IsCard}">
|
||||
<Button Classes="key" Margin="4" Height="72" Content="1" Command="{Binding KeyDigitCommand}" CommandParameter="1"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="2" Command="{Binding KeyDigitCommand}" CommandParameter="2"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="3" Command="{Binding KeyDigitCommand}" CommandParameter="3"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="4" Command="{Binding KeyDigitCommand}" CommandParameter="4"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="5" Command="{Binding KeyDigitCommand}" CommandParameter="5"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="6" Command="{Binding KeyDigitCommand}" CommandParameter="6"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="7" Command="{Binding KeyDigitCommand}" CommandParameter="7"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="8" Command="{Binding KeyDigitCommand}" CommandParameter="8"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="9" Command="{Binding KeyDigitCommand}" CommandParameter="9"/>
|
||||
<Button Classes="key" Margin="4" Height="72" FontSize="15" Content="Сброс" Command="{Binding KeyClearCommand}"/>
|
||||
<Button Classes="key" Margin="4" Height="72" Content="0" Command="{Binding KeyDigitCommand}" CommandParameter="0"/>
|
||||
<Button Classes="key" Margin="4" Height="72" FontSize="20" Content="⌫" Command="{Binding KeyBackCommand}"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="1" Command="{Binding KeyDigitCommand}" CommandParameter="1"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="2" Command="{Binding KeyDigitCommand}" CommandParameter="2"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="3" Command="{Binding KeyDigitCommand}" CommandParameter="3"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="4" Command="{Binding KeyDigitCommand}" CommandParameter="4"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="5" Command="{Binding KeyDigitCommand}" CommandParameter="5"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="6" Command="{Binding KeyDigitCommand}" CommandParameter="6"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="7" Command="{Binding KeyDigitCommand}" CommandParameter="7"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="8" Command="{Binding KeyDigitCommand}" CommandParameter="8"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="9" Command="{Binding KeyDigitCommand}" CommandParameter="9"/>
|
||||
<Button Classes="key" Margin="5" Height="72" FontSize="16" Foreground="{DynamicResource PosTextMuted}" Content="Сброс" Command="{Binding KeyClearCommand}"/>
|
||||
<Button Classes="key" Margin="5" Height="72" Content="0" Command="{Binding KeyDigitCommand}" CommandParameter="0"/>
|
||||
<Button Classes="key" Margin="5" Height="72" FontSize="20" Foreground="{DynamicResource PosTextMuted}" Content="⌫" Command="{Binding KeyBackCommand}"/>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
|
||||
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="66" FontSize="20" Command="{Binding ConfirmCommand}" IsDefault="True" IsEnabled="{Binding Sufficient}">
|
||||
<TextBlock Text="Провести и печатать · Enter" TextAlignment="Center"/>
|
||||
<!-- Действия: Назад 200px | Провести 1fr -->
|
||||
<Grid ColumnDefinitions="200,*">
|
||||
<Button Classes="pos ghost" Height="68" Margin="0,0,12,0" Command="{Binding CancelCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center">
|
||||
<Border Classes="kbd"><TextBlock Text="Esc"/></Border>
|
||||
<TextBlock Text="Назад" FontSize="17" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="pos ghost" HorizontalAlignment="Stretch" Height="44" Content="Esc · Назад" Command="{Binding CancelCommand}"/>
|
||||
<Button Grid.Column="1" Classes="pos primary" Height="68" Command="{Binding ConfirmCommand}" IsDefault="True" IsEnabled="{Binding Sufficient}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Провести и печатать" FontSize="21" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||
<Border Classes="kbd onbrand"><TextBlock Text="Enter"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -3,93 +3,107 @@
|
|||
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
||||
x:Class="foodmarket.Pos.Views.SaleView"
|
||||
x:DataType="vm:SaleViewModel">
|
||||
<Grid ColumnDefinitions="*,400" Background="{DynamicResource PosBg}">
|
||||
<Grid ColumnDefinitions="*,440" Background="{DynamicResource PosBg}">
|
||||
|
||||
<!-- ЛЕВО: поиск + избранное + корзина -->
|
||||
<Grid Grid.Column="0" RowDefinitions="Auto,Auto,*,Auto" Margin="20,18,10,18">
|
||||
<!-- ЛЕВО -->
|
||||
<Grid Grid.Column="0" RowDefinitions="Auto,Auto,Auto,*" Margin="24,16,8,16">
|
||||
|
||||
<!-- Поиск / скан -->
|
||||
<Border Grid.Row="0" Classes="card" Padding="0" Margin="0,0,0,14">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<PathIcon Grid.Column="0" Width="22" Height="22" Margin="18,0,8,0" Foreground="{DynamicResource PosTextMuted}"
|
||||
Data="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||||
<Border Grid.Row="0" Height="64" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBrand}" BorderThickness="2" CornerRadius="14"
|
||||
BoxShadow="0 0 0 4 #1F16A34A" Margin="0,0,0,16">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Path Grid.Column="0" Width="24" Height="24" Margin="20,0,14,0" Stretch="Uniform" Stroke="{DynamicResource PosBrand}" StrokeThickness="2" StrokeLineCap="Round"
|
||||
Data="M3 5v14M7 5v14M11 5v14M15 5v14M19 5v14"/>
|
||||
<TextBox Grid.Column="1" BorderThickness="0" FontSize="19" Background="Transparent" VerticalContentAlignment="Center"
|
||||
Watermark="Отсканируйте товар или начните вводить название" Text="{Binding ScanInput}">
|
||||
<TextBox.KeyBindings><KeyBinding Gesture="Enter" Command="{Binding ScanCommand}"/></TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
<Border Grid.Column="2" Classes="kbd" Margin="0,0,16,0"><TextBlock Text="F2"/></Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Избранное (быстрые товары) -->
|
||||
<StackPanel Grid.Row="1" Spacing="8" Margin="0,0,0,14" IsVisible="{Binding HasFavorites}">
|
||||
<Button Classes="pos ghost" HorizontalAlignment="Left" Height="32" Padding="12,0" FontSize="13.5" Command="{Binding ToggleFavoritesCommand}" BorderThickness="0" Background="Transparent">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<PathIcon Width="12" Height="12" Foreground="{DynamicResource PosTextMuted}" IsVisible="{Binding FavoritesOpen}" Data="M7 10l5 5 5-5z"/>
|
||||
<PathIcon Width="12" Height="12" Foreground="{DynamicResource PosTextMuted}" IsVisible="{Binding !FavoritesOpen}" Data="M10 17l5-5-5-5z"/>
|
||||
<TextBlock Text="Избранное" Foreground="{DynamicResource PosTextMuted}" FontWeight="Medium"/>
|
||||
</StackPanel>
|
||||
<!-- Заголовок избранного -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="0,0,0,12" IsVisible="{Binding HasFavorites}">
|
||||
<TextBlock Text="ИЗБРАННОЕ" FontSize="15" FontWeight="Bold" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="1" Background="Transparent" BorderThickness="0" Padding="0" Command="{Binding ToggleFavoritesCommand}" HorizontalAlignment="Right">
|
||||
<Panel>
|
||||
<TextBlock FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource PosBrandDark}" Text="Скрыть ▴" IsVisible="{Binding FavoritesOpen}"/>
|
||||
<TextBlock FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource PosBrandDark}" Text="Показать ▾" IsVisible="{Binding !FavoritesOpen}"/>
|
||||
</Panel>
|
||||
</Button>
|
||||
<ItemsControl ItemsSource="{Binding Favorites}" IsVisible="{Binding FavoritesOpen}">
|
||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="3"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Избранное -->
|
||||
<ItemsControl Grid.Row="2" ItemsSource="{Binding Favorites}" Margin="0,0,0,16" IsVisible="{Binding FavoritesOpen}">
|
||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Classes="pos ghost" Height="72" Margin="4" Padding="6,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
|
||||
<Button Height="84" Margin="6" Padding="4" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="12"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
|
||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).AddFavoriteCommand}" CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}" FontSize="14" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" HorizontalAlignment="Stretch" Foreground="{DynamicResource PosText}"/>
|
||||
<TextBlock Text="{Binding Price, StringFormat='{}{0:N0} ₸'}" FontSize="13" TextAlignment="Center" HorizontalAlignment="Stretch" Foreground="{DynamicResource PosBrand}"/>
|
||||
<StackPanel Spacing="6" HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" Foreground="{DynamicResource PosText}"/>
|
||||
<TextBlock Text="{Binding PriceText}" FontSize="15" FontWeight="Bold" TextAlignment="Center" Foreground="{DynamicResource PosBrandDark}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Корзина -->
|
||||
<Border Grid.Row="2" Classes="card" Padding="0">
|
||||
<Border Grid.Row="3" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="14">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" ColumnDefinitions="*,180,120,52" Height="40" Margin="18,6,10,0">
|
||||
<TextBlock Text="Товар" Foreground="{DynamicResource PosTextMuted}" FontSize="13" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="Количество" Foreground="{DynamicResource PosTextMuted}" FontSize="13" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" Text="Сумма" Foreground="{DynamicResource PosTextMuted}" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
<Border Grid.Row="0" Height="48" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="*,200,160,56" Margin="20,0">
|
||||
<TextBlock Text="Товар" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="Количество" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" Text="Сумма" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Пустая корзина -->
|
||||
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10" IsVisible="{Binding IsCartEmpty}">
|
||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="999" Width="72" Height="72">
|
||||
<PathIcon Width="34" Height="34" Foreground="{DynamicResource PosTextMuted}"
|
||||
Data="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/>
|
||||
<Path Width="34" Height="34" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="1.6" StrokeLineCap="Round" StrokeJoin="Round"
|
||||
Data="M6 6h15l-1.5 9h-12zM6 6l-2 -3M9 20a1 1 0 100 .01M18 20a1 1 0 100 .01"/>
|
||||
</Border>
|
||||
<TextBlock Text="Отсканируйте первый товар" Foreground="{DynamicResource PosTextMuted}" FontSize="16"/>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox Grid.Row="1" ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
|
||||
Background="Transparent" BorderThickness="0" Padding="8,4" IsVisible="{Binding !IsCartEmpty}">
|
||||
Background="Transparent" BorderThickness="0" Padding="0" IsVisible="{Binding !IsCartEmpty}">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem"><Setter Property="Padding" Value="0"/><Setter Property="Margin" Value="0,3"/><Setter Property="CornerRadius" Value="10"/></Style>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/><Setter Property="Margin" Value="0"/><Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter"><Setter Property="Background" Value="Transparent"/></Style>
|
||||
<Style Selector="Border.row"><Setter Property="Background" Value="Transparent"/></Style>
|
||||
<Style Selector="Rectangle.accent"><Setter Property="IsVisible" Value="False"/></Style>
|
||||
<Style Selector="ListBoxItem:selected Border.row"><Setter Property="Background" Value="{DynamicResource PosSelRow}"/></Style>
|
||||
<Style Selector="ListBoxItem:selected Rectangle.accent"><Setter Property="IsVisible" Value="True"/></Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SaleLineVm">
|
||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="10" Padding="12,8">
|
||||
<Grid ColumnDefinitions="*,180,120,52">
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource PosText}" FontSize="17" FontWeight="Medium" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding DiscountText}" Foreground="{DynamicResource PosBrand}" FontSize="13" IsVisible="{Binding HasDiscount}"/>
|
||||
<TextBlock Text="{Binding Price, StringFormat='{}{0:N0} ₸ / ед.'}" Foreground="{DynamicResource PosTextMuted}" FontSize="13" IsVisible="{Binding !HasDiscount}"/>
|
||||
<Border Classes="row" Height="76" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="4,*,200,160,56">
|
||||
<Rectangle Grid.Column="0" Classes="accent" Fill="{DynamicResource PosBrand}"/>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="16,0,0,0">
|
||||
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding DiscountText}" Foreground="{DynamicResource PosBrandDark}" FontSize="14" FontWeight="Medium" Margin="0,4,0,0" IsVisible="{Binding HasDiscount}"/>
|
||||
<TextBlock Text="{Binding UnitPriceText}" Foreground="{DynamicResource PosTextMuted}" FontSize="14" Margin="0,4,0,0" IsVisible="{Binding !HasDiscount}"/>
|
||||
</StackPanel>
|
||||
<!-- Степпер 44×44 -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Button Classes="key" Width="44" Height="44" FontSize="22" Content="−"
|
||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).DecLineCommand}" CommandParameter="{Binding}"/>
|
||||
<TextBlock Text="{Binding QtyText}" Foreground="{DynamicResource PosText}" FontSize="16" FontWeight="SemiBold" MinWidth="56" TextAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Button Classes="key" Width="44" Height="44" FontSize="20" Content="+"
|
||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).IncLineCommand}" CommandParameter="{Binding}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Text="{Binding LineTotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="19" FontWeight="SemiBold" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="3" Classes="key" Width="36" Height="36" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="12,0,0,0"
|
||||
<Border Grid.Column="2" Height="44" Margin="16,0" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="10" ClipToBounds="True">
|
||||
<Grid ColumnDefinitions="44,*,44">
|
||||
<Button Classes="stepbtn" Content="−" Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).DecLineCommand}" CommandParameter="{Binding}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding QtyText}" Foreground="{DynamicResource PosText}" FontSize="17" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="2" Classes="stepbtn" FontSize="20" Content="+" Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).IncLineCommand}" CommandParameter="{Binding}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="3" Text="{Binding LineTotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="4" Width="44" Height="44" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).RemoveLineCommand}" CommandParameter="{Binding}" ToolTip.Tip="Удалить">
|
||||
<PathIcon Width="18" Height="18" Foreground="{DynamicResource PosDanger}"
|
||||
Data="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
<Path Width="20" Height="20" Stretch="Uniform" Stroke="#9AA7A1" StrokeThickness="2" StrokeLineCap="Round" StrokeJoin="Round"
|
||||
Data="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
@ -98,40 +112,85 @@
|
|||
</ListBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="3" Text="{Binding Message}" Foreground="{DynamicResource PosTextMuted}" Margin="4,10,0,0" FontSize="13.5"/>
|
||||
</Grid>
|
||||
|
||||
<!-- ПРАВО: чек + оплата (компактно, без пустот) -->
|
||||
<Border Grid.Column="1" Classes="card" Margin="10,18,20,18" Padding="18">
|
||||
<StackPanel Spacing="12">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Text="Позиций" Foreground="{DynamicResource PosTextMuted}" FontSize="15"/><TextBlock Grid.Column="1" Text="{Binding ItemCount}" Foreground="{DynamicResource PosText}" FontSize="15" FontWeight="Medium"/></Grid>
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Text="Подытог" Foreground="{DynamicResource PosTextMuted}" FontSize="15"/><TextBlock Grid.Column="1" Text="{Binding Subtotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="15"/></Grid>
|
||||
<Grid ColumnDefinitions="*,Auto" IsVisible="{Binding HasAnyDiscount}"><TextBlock Text="Скидка" Foreground="{DynamicResource PosTextMuted}" FontSize="15"/><TextBlock Grid.Column="1" Text="{Binding Discount, StringFormat='−{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="15" FontWeight="Medium"/></Grid>
|
||||
</StackPanel>
|
||||
|
||||
<Border Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Padding="16,14">
|
||||
<!-- ПРАВО -->
|
||||
<Border Grid.Column="1" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="14" Margin="8,16,24,16">
|
||||
<Grid RowDefinitions="Auto,*,Auto" Margin="24">
|
||||
<StackPanel Grid.Row="0" Spacing="16">
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Text="Позиций" Foreground="{DynamicResource PosTextMuted}" FontSize="17"/><TextBlock Grid.Column="1" Text="{Binding ItemCount}" Foreground="{DynamicResource PosText}" FontSize="17" FontWeight="SemiBold"/></Grid>
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Text="Подытог" Foreground="{DynamicResource PosTextMuted}" FontSize="17"/><TextBlock Grid.Column="1" Text="{Binding Subtotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="17" FontWeight="SemiBold"/></Grid>
|
||||
<Grid ColumnDefinitions="*,Auto" IsVisible="{Binding HasAnyDiscount}"><TextBlock Text="Скидка" Foreground="{DynamicResource PosTextMuted}" FontSize="17"/><TextBlock Grid.Column="1" Text="{Binding Discount, StringFormat='−{0:N0} ₸'}" Foreground="{DynamicResource PosBrandDark}" FontSize="17" FontWeight="SemiBold"/></Grid>
|
||||
<Border Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Padding="22,20">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="ИТОГО" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="42" FontWeight="Bold"/>
|
||||
<TextBlock Text="ИТОГО" Foreground="{DynamicResource PosBrandDark}" FontSize="18" FontWeight="Bold" VerticalAlignment="Bottom"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrandDark}" FontSize="52" FontWeight="ExtraBold"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="72" FontSize="22" Command="{Binding PayCommand}">
|
||||
<TextBlock Text="Оплатить · F12" TextAlignment="Center"/>
|
||||
</Button>
|
||||
|
||||
<!-- Вторичные действия: ровная сетка 2 колонки -->
|
||||
<UniformGrid Columns="2">
|
||||
<Button Classes="pos ghost" Margin="0,0,4,8" Content="Скидка"/>
|
||||
<Button Classes="pos ghost" Margin="4,0,0,8" Content="Отложить · F7" Command="{Binding ParkCommand}"/>
|
||||
<Button Classes="pos ghost" Margin="0,0,4,8" Content="Отложенные" Command="{Binding ResumeCommand}"/>
|
||||
<Button Classes="pos ghost" Margin="4,0,0,8" Content="Возврат · F9" Command="{Binding GoReturnCommand}"/>
|
||||
</UniformGrid>
|
||||
<Button Classes="pos ghost" HorizontalAlignment="Stretch" Content="Внесение / Выплата · F10" Command="{Binding GoShiftCommand}"/>
|
||||
<Button Classes="pos danger" HorizontalAlignment="Stretch" Content="Очистить · Esc" Command="{Binding ClearCommand}"/>
|
||||
<Border BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="0,16,0,0">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Foreground="{DynamicResource PosTextMuted}" FontSize="14"><Run Text="Последний чек "/><Run Text="{Binding LastReceiptNumber}"/></TextBlock><TextBlock Grid.Column="1" Text="{Binding LastReceiptInfo}" Foreground="{DynamicResource PosText}" FontSize="14" FontWeight="SemiBold"/></Grid>
|
||||
<Grid ColumnDefinitions="*,Auto"><TextBlock Text="Чеков за смену" Foreground="{DynamicResource PosTextMuted}" FontSize="14"/><TextBlock Grid.Column="1" Text="{Binding ReceiptsInShift}" Foreground="{DynamicResource PosText}" FontSize="14" FontWeight="SemiBold"/></Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Spacing="12">
|
||||
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="76" Command="{Binding PayCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Оплатить" FontSize="24" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||
<Border Classes="kbd onbrand"><TextBlock Text="F12"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<Button Grid.Row="0" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M19 5L5 19M7 7h.01M17 17h.01"/>
|
||||
<TextBlock Text="Скидка" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F4"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Grid.Column="1" Classes="pos ghost" Height="52" Margin="5,0,0,10" Command="{Binding ParkCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeJoin="Round" Data="M6 4h12v16l-6-4-6 4z"/>
|
||||
<TextBlock Text="Отложить" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F7"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Command="{Binding ResumeCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M4 6h16M4 12h16M4 18h10"/>
|
||||
<TextBlock Text="Отложенные" VerticalAlignment="Center"/>
|
||||
<Border CornerRadius="999" MinWidth="22" Height="22" Padding="7,0" Background="{DynamicResource PosWarnSoft}" VerticalAlignment="Center" IsVisible="{Binding HasParked}">
|
||||
<TextBlock Text="{Binding ParkedCount}" FontSize="13" FontWeight="Bold" Foreground="{DynamicResource PosWarn}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Grid.Column="1" Classes="pos ghost" Height="52" Margin="5,0,0,10" Command="{Binding GoReturnCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" StrokeJoin="Round" Data="M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-3"/>
|
||||
<TextBlock Text="Возврат" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="F9"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="2" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Command="{Binding GoShiftCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M12 5v14M5 12h14"/>
|
||||
<TextBlock Text="Внесение" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="2" Grid.Column="1" Classes="pos ghost" Height="52" Margin="5,0,0,10" Command="{Binding GoShiftCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M5 12h14"/>
|
||||
<TextBlock Text="Выплата" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Classes="pos danger" Height="52" Command="{Binding ClearCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosDanger}" StrokeThickness="2" StrokeLineCap="Round" Data="M6 6l12 12M18 6L6 18"/>
|
||||
<TextBlock Text="Очистить чек" VerticalAlignment="Center"/><Border Classes="kbd"><TextBlock Text="Esc"/></Border>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
|||
Loading…
Reference in a new issue