Compare commits
2 commits
61985c1062
...
c8c0be56b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8c0be56b4 | ||
|
|
4b08782e82 |
|
Before Width: | Height: | Size: 377 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
BIN
docs/pos-screenshots/density/sale-1280x720.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
docs/pos-screenshots/density/sale-1280x800.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
docs/pos-screenshots/density/sale-1366x768.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
docs/pos-screenshots/density/sale-1920x1080.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 117 KiB |
|
|
@ -65,6 +65,27 @@
|
||||||
|
|
||||||
НЕ трогать: прод, shared-postgres, global.json, gateway nginx, sklad, другие VM.
|
НЕ трогать: прод, shared-postgres, global.json, gateway nginx, sklad, другие VM.
|
||||||
|
|
||||||
|
## Полировка по реальному окну Windows 11 (Mac-Claude, 08.10)
|
||||||
|
Замечания по `windows/windows11-sale.png` (VM 1280×800, maximized → высоты меньше, чем в headless):
|
||||||
|
1. [x] Правая панель не влезала по высоте → адаптив по высоте в `SaleView.axaml.cs`
|
||||||
|
(подписка на Bounds): уровни normal/compact/tiny — меньше отступы (margin 24→18→12,
|
||||||
|
spacing 16→10→8), скрыт блок «Последний чек», ИТОГО 52→46→40, Оплатить 76→64→56,
|
||||||
|
кнопки 52→46→42; `ClipToBounds` на панели. Проверено 1280×720/800, 1366×768, 1920×1080
|
||||||
|
(docs/pos-screenshots/density/) + реальное окно VM 1280×800 — «Очистить чек» виден, ничего не
|
||||||
|
перекрывается. 125% = меньше логическое разрешение → уровень tiny (та же логика по высоте).
|
||||||
|
2. [x] Шапка — Grid вместо StackPanel; чипы в отдельной колонке (никогда не обрезаются).
|
||||||
|
Адаптив по ширине в `MainWindow.axaml.cs`: <1400 — короткие чипы («Смена открыта»,
|
||||||
|
«На связи» без очереди); <1100 — скрыт «Магазин …».
|
||||||
|
3. [x] Иконка «Выплата» (одиночная горизонталь) схлопывалась при Stretch=Uniform → Stretch=None
|
||||||
|
с координатами в 18-боксе; «Внесение» выровнял так же. Остальные Path — 2D, в порядке.
|
||||||
|
4. [x] Hover строки — светлый #F7FBF8 (PosSelRow) вместо тёмного из темы; тема-hover на
|
||||||
|
ContentPresenter погашен.
|
||||||
|
5. [x] Плитки избранного — Grid `*,Auto`: название по центру сверху, цена прибита к низу —
|
||||||
|
у всех плиток цена на одной линии; высота 84 едина.
|
||||||
|
6. [x] Полоса горячих клавиш — `HorizontalAlignment=Left` (как в макете).
|
||||||
|
7. [x] Реальное окно Windows 11 заново (продажа компактная 1280×800 + оплата) + compare + density;
|
||||||
|
просмотрел каждый кадр — обрезок нет.
|
||||||
|
|
||||||
## Журнал
|
## Журнал
|
||||||
- 08.10 — старт; сохранены «до»-скриншоты (22), замерен baseline; планы зафиксированы.
|
- 08.10 — старт; сохранены «до»-скриншоты (22), замерен baseline; планы зафиксированы.
|
||||||
- 08.10 — светлая тема + избранное + правки выравнивания (коммиты 7a93c19, ac55da8, 29ef769, 85e486a).
|
- 08.10 — светлая тема + избранное + правки выравнивания (коммиты 7a93c19, ac55da8, 29ef769, 85e486a).
|
||||||
|
|
@ -72,3 +93,7 @@
|
||||||
экрана, бейдж отложенных, исправлена подсветка выбранной строки; CompareRunner (`--compare`) →
|
экрана, бейдж отложенных, исправлена подсветка выбранной строки; CompareRunner (`--compare`) →
|
||||||
compare-sale/compare-payment (расхождений > 4px нет); реальное окно Windows 11 (продажа + оплата).
|
compare-sale/compare-payment (расхождений > 4px нет); реальное окно Windows 11 (продажа + оплата).
|
||||||
Проверено: --uitest OK, 82 unit зелёные, smoke OK, скорость не хуже (старт 74мс, скан 13.7мс).
|
Проверено: --uitest OK, 82 unit зелёные, smoke OK, скорость не хуже (старт 74мс, скан 13.7мс).
|
||||||
|
- 08.10 — полировка по реальному окну Windows 11 (7 пунктов): адаптив правой панели по высоте
|
||||||
|
(normal/compact/tiny) и шапки по ширине, fix иконки «Выплата», светлый hover, выравнивание
|
||||||
|
плиток избранного, hotkey-полоса влево. `--density` рендерит продажу в 4 разрешениях.
|
||||||
|
Реальное окно VM 1280×800 — ничего не перекрывается. --uitest OK, 82 unit, smoke OK (старт 147мс).
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public static int Main(string[] args)
|
||||||
if (args.Contains("--smoke")) return SmokeRunner.Run(args);
|
if (args.Contains("--smoke")) return SmokeRunner.Run(args);
|
||||||
if (args.Contains("--screenshots")) return ScreenshotRunner.Run(args);
|
if (args.Contains("--screenshots")) return ScreenshotRunner.Run(args);
|
||||||
if (args.Contains("--compare")) return CompareRunner.Run(args);
|
if (args.Contains("--compare")) return CompareRunner.Run(args);
|
||||||
|
if (args.Contains("--density")) return ScreenshotRunner.RunDensity(args);
|
||||||
if (args.Contains("--uitest")) return UiTestRunner.Run(args);
|
if (args.Contains("--uitest")) return UiTestRunner.Run(args);
|
||||||
|
|
||||||
// --demo : показать реальное окно с примерами данных (для скриншота на Windows).
|
// --demo : показать реальное окно с примерами данных (для скриншота на Windows).
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,40 @@ public static int Run(string[] args)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Проверка адаптива правой панели по высоте: экран продажи в разных
|
||||||
|
/// разрешениях → docs/pos-screenshots/density/. Запуск: dotnet run -- --density</summary>
|
||||||
|
public static int RunDensity(string[] args)
|
||||||
|
{
|
||||||
|
var outDir = args.SkipWhile(a => a != "--density").Skip(1).FirstOrDefault(a => !a.StartsWith("--"))
|
||||||
|
?? Path.Combine(FindRepoRoot(), "docs", "pos-screenshots", "density");
|
||||||
|
Directory.CreateDirectory(outDir);
|
||||||
|
|
||||||
|
AppBuilder.Configure<App>()
|
||||||
|
.UseSkia()
|
||||||
|
.UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false })
|
||||||
|
.WithInterFont()
|
||||||
|
.SetupWithoutStarting();
|
||||||
|
|
||||||
|
var sizes = new (string name, int w, int h)[]
|
||||||
|
{
|
||||||
|
("1280x720", 1280, 720), ("1280x800", 1280, 800),
|
||||||
|
("1366x768", 1366, 768), ("1920x1080", 1920, 1080),
|
||||||
|
};
|
||||||
|
foreach (var (name, w, h) in sizes)
|
||||||
|
{
|
||||||
|
var window = new MainWindow { DataContext = new MainWindowViewModel() };
|
||||||
|
window.Width = w; window.Height = h;
|
||||||
|
window.Show();
|
||||||
|
for (int i = 0; i < 6; i++) Dispatcher.UIThread.RunJobs();
|
||||||
|
var frame = window.CaptureRenderedFrame();
|
||||||
|
frame?.Save(Path.Combine(outDir, $"sale-{name}.png"));
|
||||||
|
window.Close();
|
||||||
|
Console.WriteLine($"[density] sale-{name}.png");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"[done] density → {outDir}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static SaleViewModel EmptySale() => new();
|
private static SaleViewModel EmptySale() => new();
|
||||||
private static Control ShellWith(SaleViewModel sale)
|
private static Control ShellWith(SaleViewModel sale)
|
||||||
{
|
{
|
||||||
|
|
@ -165,6 +199,27 @@ private static void WriteGallery(string dir, List<(string file, string caption)>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Раздел «Адаптив» — один экран продажи в разных разрешениях (если есть).
|
||||||
|
var densDir = Path.Combine(dir, "density");
|
||||||
|
if (Directory.Exists(densDir))
|
||||||
|
{
|
||||||
|
var dens = Directory.GetFiles(densDir, "*.png").OrderBy(f => f).ToList();
|
||||||
|
if (dens.Count > 0)
|
||||||
|
{
|
||||||
|
sb.Append("<header style=\"margin-top:8px\"><h1>Адаптив — одно разрешение на кадр</h1>" +
|
||||||
|
"<div style=\"color:#94a3b8\">Правая панель сжимается по высоте (меньше отступы, " +
|
||||||
|
"скрыт блок «Последний чек», кнопки 44px), шапка — по ширине (сокращение чипов). " +
|
||||||
|
"Ничего не перекрывается на 1280×720/800, 1366×768, 1920×1080.</div></header>");
|
||||||
|
sb.Append("<div class=\"grid\">");
|
||||||
|
foreach (var d in dens)
|
||||||
|
{
|
||||||
|
var rel = "density/" + Path.GetFileName(d);
|
||||||
|
sb.Append($"<figure><a href=\"{rel}\" target=\"_blank\"><img src=\"{rel}\" loading=\"lazy\"></a><figcaption>{System.Net.WebUtility.HtmlEncode(Path.GetFileNameWithoutExtension(d))}</figcaption></figure>");
|
||||||
|
}
|
||||||
|
sb.Append("</div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sb.Append("</body></html>");
|
sb.Append("</body></html>");
|
||||||
File.WriteAllText(Path.Combine(dir, "index.html"), sb.ToString());
|
File.WriteAllText(Path.Combine(dir, "index.html"), sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,19 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
[ObservableProperty] private PaymentViewModel? _payment;
|
[ObservableProperty] private PaymentViewModel? _payment;
|
||||||
[ObservableProperty] private bool _isPaymentOpen;
|
[ObservableProperty] private bool _isPaymentOpen;
|
||||||
[ObservableProperty] private string? _updateMessage;
|
[ObservableProperty] private string? _updateMessage;
|
||||||
|
/// <summary>Узкая шапка: сокращать тексты чипов (устанавливается из code-behind по ширине окна).</summary>
|
||||||
|
[ObservableProperty] private bool _badgesCompact;
|
||||||
|
|
||||||
public string NetBadge => IsOnline ? "На связи" : "Нет связи";
|
public string NetBadge => IsOnline ? "На связи" : "Нет связи";
|
||||||
public string ShiftBadge => IsShiftOpen ? $"Смена {ShiftNumber}" : "Смена закрыта";
|
public string ShiftBadge => IsShiftOpen
|
||||||
public string QueueBadge => QueueCount > 0 ? $"· {QueueCount} в очереди" : "· очередь пуста";
|
? (BadgesCompact ? "Смена открыта" : $"Смена {ShiftNumber}")
|
||||||
|
: "Смена закрыта";
|
||||||
|
public string QueueBadge => BadgesCompact ? "" : (QueueCount > 0 ? $"· {QueueCount} в очереди" : "· очередь пуста");
|
||||||
partial void OnIsOnlineChanged(bool value) { OnPropertyChanged(nameof(NetBadge)); }
|
partial void OnIsOnlineChanged(bool value) { OnPropertyChanged(nameof(NetBadge)); }
|
||||||
partial void OnQueueCountChanged(int value) { OnPropertyChanged(nameof(QueueBadge)); }
|
partial void OnQueueCountChanged(int value) { OnPropertyChanged(nameof(QueueBadge)); }
|
||||||
partial void OnIsShiftOpenChanged(bool value) => OnPropertyChanged(nameof(ShiftBadge));
|
partial void OnIsShiftOpenChanged(bool value) => OnPropertyChanged(nameof(ShiftBadge));
|
||||||
partial void OnShiftNumberChanged(string value) => OnPropertyChanged(nameof(ShiftBadge));
|
partial void OnShiftNumberChanged(string value) => OnPropertyChanged(nameof(ShiftBadge));
|
||||||
|
partial void OnBadgesCompactChanged(bool value) { OnPropertyChanged(nameof(ShiftBadge)); OnPropertyChanged(nameof(QueueBadge)); }
|
||||||
|
|
||||||
/// <summary>Конструктор для дизайнера/скриншотов (демо-данные).</summary>
|
/// <summary>Конструктор для дизайнера/скриншотов (демо-данные).</summary>
|
||||||
public MainWindowViewModel() { CurrentPage = SaleViewModel.Sample(); CashierName = "Пётр Кассиров"; ShiftNumber = "С-2026-000012 открыта с 09:00"; IsShiftOpen = true; StoreName = "Демо-магазин"; PointName = "Касса 1"; }
|
public MainWindowViewModel() { CurrentPage = SaleViewModel.Sample(); CashierName = "Пётр Кассиров"; ShiftNumber = "С-2026-000012 открыта с 09:00"; IsShiftOpen = true; StoreName = "Демо-магазин"; PointName = "Касса 1"; }
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,31 @@
|
||||||
|
|
||||||
<!-- Верхняя полоса -->
|
<!-- Верхняя полоса -->
|
||||||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
<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">
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="20">
|
<!-- Лого (всегда) -->
|
||||||
|
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="20">
|
||||||
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||||
</TextBlock>
|
</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 Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
<!-- Мета (сворачивается по ширине) -->
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">
|
||||||
|
<StackPanel x:Name="StoreMeta" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center">
|
||||||
|
<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}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||||||
|
<Run Text="Кассир "/><Run Text="{Binding CashierName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Сообщение об обновлении (в растяжимой зоне) -->
|
||||||
|
<TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="24,0" FontSize="13" Foreground="{DynamicResource PosWarn}" Text="{Binding UpdateMessage}"
|
||||||
|
TextTrimming="CharacterEllipsis" IsVisible="{Binding UpdateMessage, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||||
|
|
||||||
|
<!-- Чипы статуса (всегда, никогда не обрезаются) -->
|
||||||
|
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||||
<Border Classes="chip" Classes.ok="{Binding IsShiftOpen}" Classes.warn="{Binding !IsShiftOpen}">
|
<Border Classes="chip" Classes.ok="{Binding IsShiftOpen}" Classes.warn="{Binding !IsShiftOpen}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||||||
|
|
@ -43,7 +50,7 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Текущая страница -->
|
<!-- Текущая страница -->
|
||||||
|
|
@ -51,7 +58,7 @@
|
||||||
|
|
||||||
<!-- Полоса горячих клавиш -->
|
<!-- Полоса горячих клавиш -->
|
||||||
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="24,0">
|
<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="28" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||||
<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="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="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="F4"/></Border><TextBlock Text="Скидка" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using foodmarket.Pos.ViewModels;
|
using foodmarket.Pos.ViewModels;
|
||||||
|
|
@ -10,6 +11,26 @@ public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
||||||
|
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
||||||
|
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
||||||
|
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTopBarDensity()
|
||||||
|
{
|
||||||
|
var w = Bounds.Width;
|
||||||
|
if (w <= 0) return;
|
||||||
|
if (DataContext is MainWindowViewModel vm) vm.BadgesCompact = w < 1400;
|
||||||
|
if (this.FindControl<StackPanel>("StoreMeta") is { } sm) sm.IsVisible = w >= 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class BoundsObserver : System.IObserver<Rect>
|
||||||
|
{
|
||||||
|
private readonly System.Action<Rect> _on;
|
||||||
|
public BoundsObserver(System.Action<Rect> on) => _on = on;
|
||||||
|
public void OnCompleted() { }
|
||||||
|
public void OnError(System.Exception error) { }
|
||||||
|
public void OnNext(Rect value) => _on(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Горячие клавиши кассы (работа без мыши). F-клавиши и Esc/Del
|
/// <summary>Горячие клавиши кассы (работа без мыши). F-клавиши и Esc/Del
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,14 @@
|
||||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Button Height="84" Margin="6" Padding="4" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="12"
|
<Button Height="84" Margin="6" Padding="8,10" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="12"
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
|
||||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).AddFavoriteCommand}" CommandParameter="{Binding}">
|
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).AddFavoriteCommand}" CommandParameter="{Binding}">
|
||||||
<StackPanel Spacing="6" HorizontalAlignment="Stretch">
|
<Grid RowDefinitions="*,Auto">
|
||||||
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" Foreground="{DynamicResource PosText}"/>
|
<TextBlock Grid.Row="0" Text="{Binding Name}" FontSize="15" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" MaxLines="2" TextTrimming="CharacterEllipsis"
|
||||||
<TextBlock Text="{Binding PriceText}" FontSize="15" FontWeight="Bold" TextAlignment="Center" Foreground="{DynamicResource PosBrandDark}"/>
|
HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource PosText}"/>
|
||||||
</StackPanel>
|
<TextBlock Grid.Row="1" Text="{Binding PriceText}" FontSize="15" FontWeight="Bold" TextAlignment="Center" HorizontalAlignment="Center" Margin="0,4,0,0" Foreground="{DynamicResource PosBrandDark}"/>
|
||||||
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
|
@ -77,8 +78,11 @@
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter"><Setter Property="Background" Value="Transparent"/></Style>
|
<Style Selector="ListBoxItem:selected /template/ ContentPresenter"><Setter Property="Background" Value="Transparent"/></Style>
|
||||||
|
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter"><Setter Property="Background" Value="Transparent"/></Style>
|
||||||
<Style Selector="Border.row"><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="Rectangle.accent"><Setter Property="IsVisible" Value="False"/></Style>
|
||||||
|
<!-- Светлая подсветка строки (как в макете #F7FBF8), а не тёмная из темы -->
|
||||||
|
<Style Selector="ListBoxItem:pointerover Border.row"><Setter Property="Background" Value="{DynamicResource PosSelRow}"/></Style>
|
||||||
<Style Selector="ListBoxItem:selected Border.row"><Setter Property="Background" Value="{DynamicResource PosSelRow}"/></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>
|
<Style Selector="ListBoxItem:selected Rectangle.accent"><Setter Property="IsVisible" Value="True"/></Style>
|
||||||
</ListBox.Styles>
|
</ListBox.Styles>
|
||||||
|
|
@ -115,19 +119,19 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- ПРАВО -->
|
<!-- ПРАВО -->
|
||||||
<Border Grid.Column="1" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="14" Margin="8,16,24,16">
|
<Border Grid.Column="1" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="14" Margin="8,16,24,16" ClipToBounds="True">
|
||||||
<Grid RowDefinitions="Auto,*,Auto" Margin="24">
|
<Grid x:Name="RightInner" RowDefinitions="Auto,*,Auto" Margin="24">
|
||||||
<StackPanel Grid.Row="0" Spacing="16">
|
<StackPanel x:Name="SummaryStack" 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 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"><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>
|
<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">
|
<Border x:Name="ItogoBorder" Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Padding="22,20">
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<TextBlock Text="ИТОГО" Foreground="{DynamicResource PosBrandDark}" FontSize="18" FontWeight="Bold" VerticalAlignment="Bottom"/>
|
<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"/>
|
<TextBlock x:Name="ItogoValue" Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrandDark}" FontSize="52" FontWeight="ExtraBold"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<Border BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="0,16,0,0">
|
<Border x:Name="LastCheckBlock" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="0,16,0,0">
|
||||||
<StackPanel Spacing="10">
|
<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 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>
|
<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>
|
||||||
|
|
@ -136,13 +140,13 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Row="2" Spacing="12">
|
<StackPanel Grid.Row="2" Spacing="12">
|
||||||
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="76" Command="{Binding PayCommand}">
|
<Button x:Name="PayButton" Classes="pos primary" HorizontalAlignment="Stretch" Height="76" Command="{Binding PayCommand}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||||
<TextBlock Text="Оплатить" FontSize="24" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
<TextBlock Text="Оплатить" FontSize="24" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||||
<Border Classes="kbd onbrand"><TextBlock Text="F12"/></Border>
|
<Border Classes="kbd onbrand"><TextBlock Text="F12"/></Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto,Auto">
|
<Grid x:Name="ActionsGrid" ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||||
<Button Grid.Row="0" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10">
|
<Button Grid.Row="0" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="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"/>
|
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M19 5L5 19M7 7h.01M17 17h.01"/>
|
||||||
|
|
@ -172,13 +176,14 @@
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Row="2" Grid.Column="0" Classes="pos ghost" Height="52" Margin="0,0,5,10" Command="{Binding GoShiftCommand}">
|
<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">
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M12 5v14M5 12h14"/>
|
<Path Width="18" Height="18" Stretch="None" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M9 3v12M3 9h12"/>
|
||||||
<TextBlock Text="Внесение" VerticalAlignment="Center"/>
|
<TextBlock Text="Внесение" VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Grid.Row="2" Grid.Column="1" Classes="pos ghost" Height="52" Margin="5,0,0,10" Command="{Binding GoShiftCommand}">
|
<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">
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
<Path Width="18" Height="18" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M5 12h14"/>
|
<!-- Минус: Stretch=None — одиночная горизонталь при Uniform схлопывается в точку -->
|
||||||
|
<Path Width="18" Height="18" Stretch="None" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="2" StrokeLineCap="Round" Data="M3 9h12"/>
|
||||||
<TextBlock Text="Выплата" VerticalAlignment="Center"/>
|
<TextBlock Text="Выплата" VerticalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,57 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace foodmarket.Pos.Views;
|
namespace foodmarket.Pos.Views;
|
||||||
|
|
||||||
public partial class SaleView : UserControl
|
public partial class SaleView : UserControl
|
||||||
{
|
{
|
||||||
public SaleView() => InitializeComponent();
|
public SaleView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
// Адаптив по высоте: в реальном окне (maximized минус заголовок/таскбар)
|
||||||
|
// высоты меньше, чем в headless. Чтобы правая панель не вылезала под полосу
|
||||||
|
// клавиш — сжимаем отступы/кнопки и прячем блок «Последний чек».
|
||||||
|
this.GetObservable(BoundsProperty).Subscribe(new AnonymousObserver(_ => ApplyDensity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _tier = -1; // 0 — обычный, 1 — компактный, 2 — минимальный
|
||||||
|
|
||||||
|
private void ApplyDensity()
|
||||||
|
{
|
||||||
|
var h = Bounds.Height;
|
||||||
|
if (h <= 0) return;
|
||||||
|
int tier = h < 560 ? 2 : h < 660 ? 1 : 0;
|
||||||
|
if (tier == _tier) return;
|
||||||
|
_tier = tier;
|
||||||
|
|
||||||
|
// Параметры по уровням плотности.
|
||||||
|
double payH = tier == 0 ? 76 : tier == 1 ? 64 : 56;
|
||||||
|
double actH = tier == 0 ? 52 : tier == 1 ? 46 : 42;
|
||||||
|
double spacing = tier == 0 ? 16 : tier == 1 ? 10 : 8;
|
||||||
|
double margin = tier == 0 ? 24 : tier == 1 ? 18 : 12;
|
||||||
|
double itogo = tier == 0 ? 52 : tier == 1 ? 46 : 40;
|
||||||
|
var itogoPad = tier == 0 ? new Thickness(22, 20) : tier == 1 ? new Thickness(20, 14) : new Thickness(16, 10);
|
||||||
|
bool showLastCheck = tier == 0;
|
||||||
|
|
||||||
|
if (this.FindControl<Border>("LastCheckBlock") is { } lc) lc.IsVisible = showLastCheck;
|
||||||
|
if (this.FindControl<Button>("PayButton") is { } pay) pay.Height = payH;
|
||||||
|
if (this.FindControl<StackPanel>("SummaryStack") is { } ss) ss.Spacing = spacing;
|
||||||
|
if (this.FindControl<Grid>("RightInner") is { } ri) ri.Margin = new Thickness(margin);
|
||||||
|
if (this.FindControl<TextBlock>("ItogoValue") is { } iv) iv.FontSize = itogo;
|
||||||
|
if (this.FindControl<Border>("ItogoBorder") is { } ib) ib.Padding = itogoPad;
|
||||||
|
if (this.FindControl<Grid>("ActionsGrid") is { } ag)
|
||||||
|
foreach (var b in ag.Children.OfType<Button>())
|
||||||
|
b.Height = actH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Лёгкий IObserver для подписки на Bounds без Rx-пакета.</summary>
|
||||||
|
private sealed class AnonymousObserver : System.IObserver<Rect>
|
||||||
|
{
|
||||||
|
private readonly System.Action<Rect> _on;
|
||||||
|
public AnonymousObserver(System.Action<Rect> on) => _on = on;
|
||||||
|
public void OnCompleted() { }
|
||||||
|
public void OnError(System.Exception error) { }
|
||||||
|
public void OnNext(Rect value) => _on(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||