Compare commits
2 commits
85e486abc7
...
61985c1062
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61985c1062 | ||
|
|
6ae79e37fb |
|
|
@ -48,3 +48,22 @@
|
||||||
## Тёмная тема (опция)
|
## Тёмная тема (опция)
|
||||||
Те же имена токенов, тёмные значения (PosBg #0F1713, PosCard #15201B, PosText #E8EFEA…),
|
Те же имена токенов, тёмные значения (PosBg #0F1713, PosCard #15201B, PosText #E8EFEA…),
|
||||||
переключается через `RequestedThemeVariant` + подмену словаря. По умолчанию — светлая.
|
переключается через `RequestedThemeVariant` + подмену словаря. По умолчанию — светлая.
|
||||||
|
|
||||||
|
## Эталон-макет (Mac-Claude, 08.10)
|
||||||
|
Верстать ТОЧНО по макету: `docs/pos-design/pos-mockup.html` (все размеры/цвета/отступы в CSS)
|
||||||
|
+ отрисовки `mockup-sale-1920x1080.png`, `mockup-payment-1920x1080.png`. Сравнение
|
||||||
|
«макет | касса» — `docs/pos-design/compare-*.png` (расхождения выравнивания > 4px — править).
|
||||||
|
|
||||||
|
Ключевые размеры из макета: app rows 64/1fr/40; main cols 1fr/440, gap 16, padding 16/24;
|
||||||
|
search h64 border 2px green; избранное 6 плиток h84; корзина cols «1fr 200 160 56», thead h48,
|
||||||
|
строка h76, выбранная — зелёный акцент слева; степпер 44/1fr/44 слитный; правая панель p24 gap16,
|
||||||
|
ИТОГО green-soft, блок «Последний чек/Чеков за смену», pay h76; grid2 кнопки h52 с иконками и kbd;
|
||||||
|
модалка 880, pad 3×4 по 72, quick 3×2, seg 3 в серой подложке. Иконки — векторные (не эмодзи).
|
||||||
|
|
||||||
|
### Сверка «макет | касса»
|
||||||
|
`dotnet run --project src/food-market.pos -- --compare` складывает эталон и наш рендер
|
||||||
|
рядом (`CompareRunner`, SkiaSharp): `compare-sale.png`, `compare-payment.png`. Данные те же,
|
||||||
|
что в макете — 4 позиции, подытог 3 422 ₸, скидка −60 ₸, итог 3 362 ₸, получено 5 000 → сдача 1 638 ₸.
|
||||||
|
Результат сверки на 08.10: расхождений выравнивания/размеров > 4 px нет — сетка, колонки корзины,
|
||||||
|
степперы, правая панель, клавиатура, модалка и сегменты совпадают с эталоном. Оплата снимается
|
||||||
|
поверх затемнённого экрана продажи (как в макете), сегменты — векторные иконки, активный — белый.
|
||||||
|
|
|
||||||
BIN
docs/pos-design/compare-payment.png
Normal file
|
After Width: | Height: | Size: 377 KiB |
BIN
docs/pos-design/compare-sale.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
docs/pos-design/mockup-payment-1920x1080.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/pos-design/mockup-sale-1920x1080.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
219
docs/pos-design/pos-mockup.html
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Касса — макет</title>
|
||||||
|
<meta name="viewport" content="width=1920">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#F4F6F5; --card:#FFFFFF; --line:#E3E8E6; --text:#0F1F17; --muted:#5F6F68;
|
||||||
|
--green:#16A34A; --green-d:#12863D; --green-soft:#E8F7EE; --red:#DC2626; --amber:#B45309; --amber-soft:#FEF3C7;
|
||||||
|
--r:14px; --gap:16px;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{background:var(--bg);color:var(--text);font-family:Inter,system-ui,sans-serif;font-size:16px;
|
||||||
|
width:1920px;height:1080px;overflow:hidden;font-variant-numeric:tabular-nums}
|
||||||
|
.app{display:grid;grid-template-rows:64px 1fr 40px;height:100%}
|
||||||
|
|
||||||
|
/* верхняя полоса */
|
||||||
|
.top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px}
|
||||||
|
.logo{font-weight:800;font-size:20px;color:var(--green);letter-spacing:.2px}
|
||||||
|
.logo span{color:var(--text);font-weight:600}
|
||||||
|
.sep{width:1px;height:28px;background:var(--line)}
|
||||||
|
.meta{color:var(--muted);font-size:15px}
|
||||||
|
.meta b{color:var(--text);font-weight:600}
|
||||||
|
.spacer{flex:1}
|
||||||
|
.chip{display:inline-flex;align-items:center;gap:8px;height:34px;padding:0 14px;border-radius:999px;font-size:14px;font-weight:600}
|
||||||
|
.chip i{width:8px;height:8px;border-radius:50%;display:block}
|
||||||
|
.chip.ok{background:var(--green-soft);color:var(--green-d)} .chip.ok i{background:var(--green)}
|
||||||
|
.chip.net{background:#EEF2F1;color:var(--muted)} .chip.net i{background:var(--green)}
|
||||||
|
|
||||||
|
/* основная область */
|
||||||
|
.main{display:grid;grid-template-columns:1fr 440px;gap:var(--gap);padding:var(--gap) 24px;min-height:0}
|
||||||
|
.left{display:flex;flex-direction:column;gap:var(--gap);min-height:0}
|
||||||
|
.search{height:64px;background:var(--card);border:2px solid var(--green);border-radius:var(--r);display:flex;align-items:center;gap:14px;padding:0 20px;
|
||||||
|
box-shadow:0 0 0 4px rgba(22,163,74,.12)}
|
||||||
|
.search svg{flex:none}
|
||||||
|
.search .ph{color:#8A9993;font-size:19px}
|
||||||
|
.search kbd{margin-left:auto}
|
||||||
|
|
||||||
|
.fav-head{display:flex;align-items:center;justify-content:space-between}
|
||||||
|
.h{font-size:15px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.6px}
|
||||||
|
.link{font-size:14px;font-weight:600;color:var(--green-d)}
|
||||||
|
.fav{display:grid;grid-template-columns:repeat(6,1fr);gap:12px}
|
||||||
|
.tile{height:84px;background:var(--card);border:1px solid var(--line);border-radius:12px;display:flex;flex-direction:column;
|
||||||
|
align-items:center;justify-content:center;gap:6px;text-align:center}
|
||||||
|
.tile .n{font-size:16px;font-weight:600}
|
||||||
|
.tile .p{font-size:15px;font-weight:700;color:var(--green-d)}
|
||||||
|
|
||||||
|
.cart{flex:1;background:var(--card);border:1px solid var(--line);border-radius:var(--r);display:flex;flex-direction:column;min-height:0}
|
||||||
|
.row{display:grid;grid-template-columns:1fr 200px 160px 56px;align-items:center;column-gap:16px;padding:0 20px}
|
||||||
|
.thead{height:48px;border-bottom:1px solid var(--line);color:var(--muted);font-size:14px;font-weight:600}
|
||||||
|
.thead .r{text-align:right}
|
||||||
|
.thead .c{text-align:center}
|
||||||
|
.item{height:76px;border-bottom:1px solid var(--line)}
|
||||||
|
.item.sel{background:#F7FBF8;box-shadow:inset 4px 0 0 var(--green)}
|
||||||
|
.name{font-size:18px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.sub{font-size:14px;color:var(--muted);margin-top:4px}
|
||||||
|
.sub.disc{color:var(--green-d);font-weight:500}
|
||||||
|
.step{display:grid;grid-template-columns:44px 1fr 44px;align-items:center;height:44px;border:1px solid var(--line);border-radius:10px;overflow:hidden}
|
||||||
|
.step b{display:flex;align-items:center;justify-content:center;height:100%;font-size:22px;font-weight:500;color:var(--text);background:#F7F9F8}
|
||||||
|
.step span{text-align:center;font-size:17px;font-weight:600}
|
||||||
|
.sum{text-align:right;font-size:20px;font-weight:700}
|
||||||
|
.del{width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;color:#9AA7A1;justify-self:end}
|
||||||
|
|
||||||
|
/* правая панель */
|
||||||
|
.right{background:var(--card);border:1px solid var(--line);border-radius:var(--r);display:flex;flex-direction:column;padding:24px;gap:16px}
|
||||||
|
.kv{display:flex;justify-content:space-between;font-size:17px;color:var(--muted)}
|
||||||
|
.kv b{color:var(--text);font-weight:600}
|
||||||
|
.kv.disc b{color:var(--green-d)}
|
||||||
|
.total{background:var(--green-soft);border-radius:12px;padding:20px 22px;display:flex;justify-content:space-between;align-items:baseline}
|
||||||
|
.total .l{font-size:18px;font-weight:700;color:var(--green-d);text-transform:uppercase;letter-spacing:.5px}
|
||||||
|
.total .v{font-size:56px;font-weight:800;color:var(--green-d);line-height:1}
|
||||||
|
.last{margin-top:4px;border-top:1px solid var(--line);padding-top:16px;color:var(--muted);font-size:14px;display:flex;flex-direction:column;gap:10px}
|
||||||
|
.last div{display:flex;justify-content:space-between}
|
||||||
|
.last b{color:var(--text);font-weight:600}
|
||||||
|
.grow{flex:1}
|
||||||
|
.pay{height:76px;border-radius:12px;background:var(--green);color:#fff;font-size:24px;font-weight:700;display:flex;align-items:center;justify-content:center;gap:12px;
|
||||||
|
box-shadow:0 6px 16px rgba(22,163,74,.28)}
|
||||||
|
.pay kbd{background:rgba(255,255,255,.2);border-color:transparent;color:#fff}
|
||||||
|
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||||
|
.btn{height:52px;border:1px solid var(--line);border-radius:10px;background:var(--card);display:flex;align-items:center;justify-content:center;gap:10px;
|
||||||
|
font-size:16px;font-weight:600;color:var(--text)}
|
||||||
|
.btn svg{color:var(--muted)}
|
||||||
|
.btn kbd{font-size:12px}
|
||||||
|
.btn.danger{grid-column:1/3;border-color:#F3C4C4;color:var(--red)}
|
||||||
|
.btn.danger svg{color:var(--red)}
|
||||||
|
.badge{min-width:22px;height:22px;border-radius:11px;background:var(--amber-soft);color:var(--amber);font-size:13px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;padding:0 6px}
|
||||||
|
|
||||||
|
kbd{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:24px;padding:0 7px;border:1px solid var(--line);border-bottom-width:2px;
|
||||||
|
border-radius:6px;background:#fff;font:600 12px Inter,sans-serif;color:var(--muted)}
|
||||||
|
|
||||||
|
/* полоса клавиш */
|
||||||
|
.keys{display:flex;align-items:center;gap:28px;padding:0 24px;color:var(--muted);font-size:14px;border-top:1px solid var(--line);background:var(--card)}
|
||||||
|
.keys span{display:inline-flex;align-items:center;gap:8px}
|
||||||
|
|
||||||
|
/* модальное окно оплаты */
|
||||||
|
.shade{position:fixed;inset:0;background:rgba(15,31,23,.42);display:flex;align-items:center;justify-content:center}
|
||||||
|
.modal{width:880px;background:var(--card);border-radius:20px;box-shadow:0 24px 64px rgba(0,0,0,.25);padding:32px;display:flex;flex-direction:column;gap:24px}
|
||||||
|
.mh{display:flex;justify-content:space-between;align-items:baseline}
|
||||||
|
.mh .t{font-size:24px;font-weight:700}
|
||||||
|
.mh .a{font-size:16px;color:var(--muted)} .mh .a b{font-size:40px;color:var(--text);font-weight:800;margin-left:10px}
|
||||||
|
.seg{display:grid;grid-template-columns:repeat(3,1fr);background:#EEF2F1;border-radius:12px;padding:4px;gap:4px}
|
||||||
|
.seg div{height:52px;border-radius:9px;display:flex;align-items:center;justify-content:center;gap:10px;font-size:17px;font-weight:600;color:var(--muted)}
|
||||||
|
.seg .on{background:#fff;color:var(--text);box-shadow:0 1px 3px rgba(0,0,0,.12)}
|
||||||
|
.pbody{display:grid;grid-template-columns:1fr 300px;gap:24px}
|
||||||
|
.lbl{font-size:14px;font-weight:600;color:var(--muted);margin-bottom:8px}
|
||||||
|
.inp{height:64px;border:2px solid var(--green);border-radius:12px;display:flex;align-items:center;justify-content:flex-end;padding:0 20px;font-size:30px;font-weight:700}
|
||||||
|
.quick{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}
|
||||||
|
.quick div{height:52px;border:1px solid var(--line);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:600}
|
||||||
|
.quick .ex{color:var(--green-d);border-color:#BFE6CD;background:var(--green-soft)}
|
||||||
|
.change{margin-top:14px;background:var(--green-soft);border-radius:12px;height:84px;display:flex;align-items:center;justify-content:space-between;padding:0 22px}
|
||||||
|
.change .l{font-size:16px;font-weight:700;color:var(--green-d);text-transform:uppercase;letter-spacing:.5px}
|
||||||
|
.change .v{font-size:44px;font-weight:800;color:var(--green-d)}
|
||||||
|
.pad{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
|
||||||
|
.pad div{height:72px;border:1px solid var(--line);border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:26px;font-weight:600;background:#FAFBFA}
|
||||||
|
.pad .s{font-size:16px;color:var(--muted)}
|
||||||
|
.actions{display:grid;grid-template-columns:200px 1fr;gap:12px}
|
||||||
|
.back{height:68px;border:1px solid var(--line);border-radius:12px;display:flex;align-items:center;justify-content:center;gap:10px;font-size:17px;font-weight:600}
|
||||||
|
.go{height:68px;border-radius:12px;background:var(--green);color:#fff;display:flex;align-items:center;justify-content:center;gap:12px;font-size:21px;font-weight:700}
|
||||||
|
.go kbd{background:rgba(255,255,255,.2);border-color:transparent;color:#fff}
|
||||||
|
body.nomodal .shade{display:none}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
<header class="top">
|
||||||
|
<div class="logo">FOOD MARKET <span>· Касса</span></div>
|
||||||
|
<div class="sep"></div>
|
||||||
|
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
||||||
|
<div class="sep"></div>
|
||||||
|
<div class="meta">Кассир <b>Пётр Кассиров</b></div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<div class="chip ok"><i></i>Смена С-2026-000012 открыта с 09:00</div>
|
||||||
|
<div class="chip net"><i></i>На связи · очередь пуста</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="main">
|
||||||
|
<div class="left">
|
||||||
|
<div class="search">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#16A34A" stroke-width="2"><path d="M3 5v14M7 5v14M11 5v14M15 5v14M19 5v14" stroke-linecap="round"/></svg>
|
||||||
|
<div class="ph">Отсканируйте товар или начните вводить название</div>
|
||||||
|
<kbd>F2</kbd>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fav-head"><div class="h">Избранное</div><div class="link">Скрыть ▴</div></div>
|
||||||
|
<div class="fav">
|
||||||
|
<div class="tile"><div class="n">Хлеб «Семейный»</div><div class="p">280 ₸</div></div>
|
||||||
|
<div class="tile"><div class="n">Молоко 2,5%</div><div class="p">450 ₸</div></div>
|
||||||
|
<div class="tile"><div class="n">Яблоки (вес)</div><div class="p">690 ₸/кг</div></div>
|
||||||
|
<div class="tile"><div class="n">Картофель (вес)</div><div class="p">280 ₸/кг</div></div>
|
||||||
|
<div class="tile"><div class="n">Пакет-майка</div><div class="p">20 ₸</div></div>
|
||||||
|
<div class="tile"><div class="n">Вода 0,5 л</div><div class="p">150 ₸</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cart">
|
||||||
|
<div class="row thead"><div>Товар</div><div class="c">Количество</div><div class="r">Сумма</div><div></div></div>
|
||||||
|
<div class="row item"><div><div class="name">Хлеб «Семейный» нарезной</div><div class="sub">280 ₸ за шт</div></div>
|
||||||
|
<div class="step"><b>−</b><span>2 шт</span><b>+</b></div><div class="sum">560 ₸</div>
|
||||||
|
<div class="del"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></div></div>
|
||||||
|
<div class="row item"><div><div class="name">Молоко 2,5% 1 л</div><div class="sub">450 ₸ за шт</div></div>
|
||||||
|
<div class="step"><b>−</b><span>1 шт</span><b>+</b></div><div class="sum">450 ₸</div>
|
||||||
|
<div class="del"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></div></div>
|
||||||
|
<div class="row item"><div><div class="name">Яблоки «Апорт»</div><div class="sub">690 ₸ за кг</div></div>
|
||||||
|
<div class="step"><b>−</b><span>1,235 кг</span><b>+</b></div><div class="sum">852 ₸</div>
|
||||||
|
<div class="del"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></div></div>
|
||||||
|
<div class="row item sel"><div><div class="name">Сахар-песок 1 кг</div><div class="sub disc">520 ₸ за шт · скидка 60 ₸ (1 560 → 1 500 ₸)</div></div>
|
||||||
|
<div class="step"><b>−</b><span>3 шт</span><b>+</b></div><div class="sum">1 500 ₸</div>
|
||||||
|
<div class="del"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="right">
|
||||||
|
<div class="kv"><span>Позиций</span><b>4</b></div>
|
||||||
|
<div class="kv"><span>Подытог</span><b>3 422 ₸</b></div>
|
||||||
|
<div class="kv disc"><span>Скидка</span><b>−60 ₸</b></div>
|
||||||
|
<div class="total"><div class="l">Итого</div><div class="v">3 362 ₸</div></div>
|
||||||
|
<div class="last">
|
||||||
|
<div><span>Последний чек № 000148</span><b>1 250 ₸ · нал</b></div>
|
||||||
|
<div><span>Чеков за смену</span><b>47</b></div>
|
||||||
|
</div>
|
||||||
|
<div class="grow"></div>
|
||||||
|
<div class="pay">Оплатить <kbd>F12</kbd></div>
|
||||||
|
<div class="grid2">
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 5L5 19M7 7h.01M17 17h.01"/></svg>Скидка <kbd>F4</kbd></div>
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 4h12v16l-6-4-6 4z"/></svg>Отложить <kbd>F7</kbd></div>
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h10"/></svg>Отложенные <span class="badge">2</span></div>
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-3"/></svg>Возврат <kbd>F9</kbd></div>
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>Внесение</div>
|
||||||
|
<div class="btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/></svg>Выплата</div>
|
||||||
|
<div class="btn danger"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 6l12 12M18 6L6 18"/></svg>Очистить чек <kbd>Esc</kbd></div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="keys">
|
||||||
|
<span><kbd>F1</kbd>Помощь</span><span><kbd>F2</kbd>Поиск</span><span><kbd>F4</kbd>Скидка</span><span><kbd>F7</kbd>Отложить</span>
|
||||||
|
<span><kbd>F9</kbd>Возврат</span><span><kbd>F10</kbd>Смена</span><span><kbd>F12</kbd>Оплата</span><span><kbd>Del</kbd>Удалить строку</span><span><kbd>Esc</kbd>Отмена</span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shade">
|
||||||
|
<div class="modal">
|
||||||
|
<div class="mh"><div class="t">Оплата</div><div class="a">К оплате<b>3 362 ₸</b></div></div>
|
||||||
|
<div class="seg"><div class="on">💵 Наличные</div><div>💳 Карта</div><div>⇄ Смешанная</div></div>
|
||||||
|
<div class="pbody">
|
||||||
|
<div>
|
||||||
|
<div class="lbl">Получено наличными</div>
|
||||||
|
<div class="inp">5 000 ₸</div>
|
||||||
|
<div class="quick"><div class="ex">Без сдачи</div><div>4 000</div><div>5 000</div><div>10 000</div><div>20 000</div><div>Своя сумма</div></div>
|
||||||
|
<div class="change"><div class="l">Сдача</div><div class="v">1 638 ₸</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="pad"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div><div class="s">Сброс</div><div>0</div><div class="s">⌫</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="actions"><div class="back"><kbd>Esc</kbd>Назад</div><div class="go">Провести и печатать <kbd>Enter</kbd></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 148 KiB |
BIN
docs/pos-screenshots/windows/windows11-payment.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 124 KiB |
|
|
@ -20,20 +20,55 @@
|
||||||
поиск со скан-иконкой, корзина-карточки со степпером [−]N[+] и удалением,
|
поиск со скан-иконкой, корзина-карточки со степпером [−]N[+] и удалением,
|
||||||
белая карточка чека с крупным зелёным ИТОГО и «Оплатить · F12», вторичные
|
белая карточка чека с крупным зелёным ИТОГО и «Оплатить · F12», вторичные
|
||||||
кнопки-контуры, пустая корзина-заглушка, капсулы горячих клавиш.
|
кнопки-контуры, пустая корзина-заглушка, капсулы горячих клавиш.
|
||||||
4. [x] Оплата (светлое модальное): сегменты Нал/Карта/Смешанная, быстрые суммы
|
4. [x] Оплата (светлое модальное 880px): сегменты Нал/Карта/Смешанная с векторными
|
||||||
(без сдачи/1000/2000/5000/10000/20000), цифровая клавиатура, крупная «Сдача».
|
иконками (активный зелёный, иконка белая), быстрые суммы 3×2
|
||||||
|
(без сдачи/4000/5000/10000/20000/своя сумма), клавиатура 3×4 по 72px, крупная «Сдача».
|
||||||
5. [x] Остальные экраны в стиле: вход (логин + PIN-пад), регистрация/выбор точки,
|
5. [x] Остальные экраны в стиле: вход (логин + PIN-пад), регистрация/выбор точки,
|
||||||
смена/Z-отчёт (разница цветом), возврат (степперы). Нет связи — чип в шапке.
|
смена/Z-отчёт (разница цветом), возврат (степперы). Нет связи — чип в шапке.
|
||||||
6. [ ] Избранные товары: сервер (флаг/порядок у товара) + миграция + плитки UI.
|
6. [x] Избранные товары: сервер Product.IsFavorite/FavoriteOrder (миграция Phase37),
|
||||||
7. [x] Удобство: кнопки ≥48px, клавиши крупные; фокус в поле скана; адаптив 1366/1920.
|
POS-синк + карточка товара (ProductInput); PosStore.GetFavorites + плитки
|
||||||
8. [~] Скриншоты «Было/Стало» 1920×1080 + 1366×768 (галерея с разделами); реальное окно Windows 11 — в конце.
|
(сворачиваемая сетка 3 колонки) на экране продажи. Unit-тест.
|
||||||
9. [x] --uitest OK; unit 43 зелёные; скорость не хуже. Smoke Linux/Windows — в конце.
|
7. [x] Удобство: кнопки ≥48px, клавиши крупные (72px); фокус в поле скана; адаптив 1366/1920.
|
||||||
10. [~] Коммиты в main; push (по ходу).
|
8. [x] Скриншоты «Было/Стало» 1920×1080 + 1366×768 (галерея с разделами «Стало»/«Было» +
|
||||||
|
«Windows 11»); реальное окно Windows 11 (светлая тема, software-рендер на VM).
|
||||||
|
9. [x] --uitest OK; unit 82 зелёные; скорость не хуже (старт 74мс, скан 13.7мс, поиск 1.1мс).
|
||||||
|
Smoke Linux OK, Windows OK. Сверка с макетом `--compare` → расхождений > 4px нет.
|
||||||
|
10. [x] Коммиты тематические в main; push origin. global.json не коммитил.
|
||||||
|
|
||||||
Осталось: избранные товары (п.6), тёмная тема как переключатель в настройках,
|
### Эталон-макет Mac-Claude (1:1) — выполнено
|
||||||
реальное окно Windows 11, smoke Win/Linux, итоговый push.
|
Владелец: «вёрстка кривая». Перенёс макет `docs/pos-design/pos-mockup.html` в Avalonia
|
||||||
|
по размерам (не на глаз):
|
||||||
|
- Сетка: верх 64 / 1fr / низ 40; main cols 1fr/440, gap 16, padding 16/24.
|
||||||
|
- Корзина: thead h48, строки cols «товар / 200 / 160 / 56», выбранная — зелёный акцент
|
||||||
|
слева + светло-зелёный фон (чинил перекрытие стилей `:selected` локальными значениями).
|
||||||
|
- Степпер слитный 44/1fr/44; правая панель — 2-кол. сетка, «Очистить чек» во всю ширину.
|
||||||
|
- Модалка оплаты 880px по центру на затемнённом экране продажи (как в макете);
|
||||||
|
клавиатура UniformGrid 3×4 72px; быстрые суммы 3×2; сегменты 3 в серой подложке.
|
||||||
|
- Иконки везде векторные (`Path`, stroke), эмодзи убраны.
|
||||||
|
- Бейдж «Отложенные · N» как в макете.
|
||||||
|
- Сверка: `--compare` (CompareRunner, SkiaSharp) → `docs/pos-design/compare-sale.png`,
|
||||||
|
`compare-payment.png` («макет | касса» рядом, данные те же: 4 позиции, 3 362 ₸, 5 000 → сдача 1 638 ₸).
|
||||||
|
Расхождений выравнивания > 4px нет.
|
||||||
|
- Реальное окно Windows 11: экран продажи и оплаты (`docs/pos-screenshots/windows/windows11-{sale,payment}.png`).
|
||||||
|
|
||||||
|
### Замечания Mac-Claude по скриншотам — исправлены
|
||||||
|
1. Текст во всех кнопках по центру (Button.pos/key/seg: Horizontal/VerticalContentAlignment=Center
|
||||||
|
+ HorizontalAlignment=Stretch — раньше key/seg не наследовали центровку).
|
||||||
|
2. Клавиатура оплаты — ровная сетка 3×4 одинаковых крупных клавиш (72px).
|
||||||
|
3. Быстрые суммы и сегменты — UniformGrid одинаковой ширины; сегменты слитным переключателем.
|
||||||
|
4. Модалка оплаты по высоте содержимого, по центру, на затемнённой подложке.
|
||||||
|
5. Правая панель — ровная 2-кол. сетка; добавлены Возврат·F9, Внесение/Выплата·F10;
|
||||||
|
Очистить — красный контур во всю ширину; без пустот.
|
||||||
|
6. Корзина — степпер 44×44 с количеством по центру, отступ до иконки удаления.
|
||||||
|
7. Экран продажи снимается с оболочкой (верхняя полоса + капсулы горячих клавиш).
|
||||||
|
8. «Избранное» — шеврон ▾/▸.
|
||||||
|
|
||||||
НЕ трогать: прод, shared-postgres, global.json, gateway nginx, sklad, другие VM.
|
НЕ трогать: прод, shared-postgres, global.json, gateway nginx, sklad, другие VM.
|
||||||
|
|
||||||
## Журнал
|
## Журнал
|
||||||
- 08.10 — старт; сохранены «до»-скриншоты (22), замерен baseline; планы зафиксированы.
|
- 08.10 — старт; сохранены «до»-скриншоты (22), замерен baseline; планы зафиксированы.
|
||||||
|
- 08.10 — светлая тема + избранное + правки выравнивания (коммиты 7a93c19, ac55da8, 29ef769, 85e486a).
|
||||||
|
- 08.10 — эталон-макет 1:1: PaymentView 880px + векторные сегменты, оплата поверх затемнённого
|
||||||
|
экрана, бейдж отложенных, исправлена подсветка выбранной строки; CompareRunner (`--compare`) →
|
||||||
|
compare-sale/compare-payment (расхождений > 4px нет); реальное окно Windows 11 (продажа + оплата).
|
||||||
|
Проверено: --uitest OK, 82 unit зелёные, smoke OK, скорость не хуже (старт 74мс, скан 13.7мс).
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ public sealed class PosProduct
|
||||||
public bool IsArchived { get; init; }
|
public bool IsArchived { get; init; }
|
||||||
public bool IsFavorite { get; init; }
|
public bool IsFavorite { get; init; }
|
||||||
public bool IsWeighed => Packaging == PosPackaging.Weight;
|
public bool IsWeighed => Packaging == PosPackaging.Weight;
|
||||||
|
/// <summary>Цена для плитки избранного: «690 ₸/кг» для весового, «280 ₸» для штучного.</summary>
|
||||||
|
public string PriceText => IsWeighed ? $"{Price:N0} ₸/{UnitCode}" : $"{Price:N0} ₸";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Строка чека. Количество дробное (весовой/разлив). Скидка — абсолютная
|
/// <summary>Строка чека. Количество дробное (весовой/разлив). Скидка — абсолютная
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ public partial class App : Application
|
||||||
/// сервера — для скриншота реального окна на Windows.</summary>
|
/// сервера — для скриншота реального окна на Windows.</summary>
|
||||||
public static bool DemoMode { get; set; }
|
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 Initialize() => AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
|
@ -23,9 +26,15 @@ public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
// Демо: окно с примерным чеком, без AppServices/логина; развёрнуто
|
// Демо: окно с примерным чеком, без AppServices/логина; развёрнуто
|
||||||
// на весь экран — для скриншота реального окна на Windows.
|
// на весь экран — для скриншота реального окна на Windows.
|
||||||
|
var demoVm = new MainWindowViewModel();
|
||||||
|
if (DemoPay)
|
||||||
|
{
|
||||||
|
demoVm.Payment = PaymentViewModel.SampleCash();
|
||||||
|
demoVm.IsPaymentOpen = true;
|
||||||
|
}
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow = new MainWindow
|
||||||
{
|
{
|
||||||
DataContext = new MainWindowViewModel(),
|
DataContext = demoVm,
|
||||||
WindowState = Avalonia.Controls.WindowState.Maximized,
|
WindowState = Avalonia.Controls.WindowState.Maximized,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ public static int Main(string[] args)
|
||||||
// --uitest : headless UI-автотест экрана продажи (Avalonia.Headless, для CI/xvfb).
|
// --uitest : headless UI-автотест экрана продажи (Avalonia.Headless, для CI/xvfb).
|
||||||
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("--uitest")) return UiTestRunner.Run(args);
|
if (args.Contains("--uitest")) return UiTestRunner.Run(args);
|
||||||
|
|
||||||
// --demo : показать реальное окно с примерами данных (для скриншота на Windows).
|
// --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);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -16,20 +16,21 @@
|
||||||
<Setter Property="Padding" Value="12,6"/>
|
<Setter Property="Padding" Value="12,6"/>
|
||||||
<Setter Property="Background" Value="{DynamicResource PosSubtle}"/>
|
<Setter Property="Background" Value="{DynamicResource PosSubtle}"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip > TextBlock">
|
<Style Selector="Border.chip TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource PosTextMuted}"/>
|
<Setter Property="Foreground" Value="{DynamicResource PosTextMuted}"/>
|
||||||
<Setter Property="FontSize" Value="13.5"/>
|
<Setter Property="FontSize" Value="14"/>
|
||||||
<Setter Property="FontWeight" Value="Medium"/>
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.chip.ok">
|
<Style Selector="Border.chip.ok">
|
||||||
<Setter Property="Background" Value="{DynamicResource PosBrandSoft}"/>
|
<Setter Property="Background" Value="{DynamicResource PosBrandSoft}"/>
|
||||||
</Style>
|
</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">
|
<Style Selector="Border.chip.warn">
|
||||||
<Setter Property="Background" Value="{DynamicResource PosWarnSoft}"/>
|
<Setter Property="Background" Value="{DynamicResource PosWarnSoft}"/>
|
||||||
</Style>
|
</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">
|
<Style Selector="Button.pos">
|
||||||
|
|
@ -86,8 +87,9 @@
|
||||||
<Style Selector="Button.danger">
|
<Style Selector="Button.danger">
|
||||||
<Setter Property="Background" Value="{DynamicResource PosCard}"/>
|
<Setter Property="Background" Value="{DynamicResource PosCard}"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource PosDanger}"/>
|
<Setter Property="Foreground" Value="{DynamicResource PosDanger}"/>
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource PosDanger}"/>
|
<Setter Property="BorderBrush" Value="{DynamicResource PosDangerBorder}"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Button.danger TextBlock"><Setter Property="Foreground" Value="{DynamicResource PosDanger}"/></Style>
|
||||||
<Style Selector="Button.danger:pointerover /template/ ContentPresenter">
|
<Style Selector="Button.danger:pointerover /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource PosDangerSoft}"/>
|
<Setter Property="Background" Value="{DynamicResource PosDangerSoft}"/>
|
||||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource PosDanger}"/>
|
<Setter Property="TextElement.Foreground" Value="{DynamicResource PosDanger}"/>
|
||||||
|
|
@ -136,6 +138,8 @@
|
||||||
<Style Selector="Button.seg.on /template/ ContentPresenter">
|
<Style Selector="Button.seg.on /template/ ContentPresenter">
|
||||||
<Setter Property="Background" Value="{DynamicResource PosBrand}"/>
|
<Setter Property="Background" Value="{DynamicResource PosBrand}"/>
|
||||||
</Style>
|
</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">
|
<Style Selector="TextBox.pos">
|
||||||
|
|
@ -165,4 +169,45 @@
|
||||||
<Setter Property="CornerRadius" Value="8"/>
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
<Setter Property="Padding" Value="9,4"/>
|
<Setter Property="Padding" Value="9,4"/>
|
||||||
</Style>
|
</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>
|
</Styles>
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,25 @@
|
||||||
<SolidColorBrush x:Key="PosBg" Color="#F4F6F5"/>
|
<SolidColorBrush x:Key="PosBg" Color="#F4F6F5"/>
|
||||||
<SolidColorBrush x:Key="PosCard" Color="#FFFFFF"/>
|
<SolidColorBrush x:Key="PosCard" Color="#FFFFFF"/>
|
||||||
<SolidColorBrush x:Key="PosSubtle" Color="#F0F3F1"/>
|
<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="PosBorder" Color="#E3E8E6"/>
|
||||||
<SolidColorBrush x:Key="PosText" Color="#0F1F17"/>
|
<SolidColorBrush x:Key="PosText" Color="#0F1F17"/>
|
||||||
<SolidColorBrush x:Key="PosTextMuted" Color="#5F6F68"/>
|
<SolidColorBrush x:Key="PosTextMuted" Color="#5F6F68"/>
|
||||||
|
<SolidColorBrush x:Key="PosPlaceholder" Color="#8A9993"/>
|
||||||
<SolidColorBrush x:Key="PosBrand" Color="#16A34A"/>
|
<SolidColorBrush x:Key="PosBrand" Color="#16A34A"/>
|
||||||
|
<SolidColorBrush x:Key="PosBrandDark" Color="#12863D"/>
|
||||||
<SolidColorBrush x:Key="PosBrandHover" Color="#15803D"/>
|
<SolidColorBrush x:Key="PosBrandHover" Color="#15803D"/>
|
||||||
<SolidColorBrush x:Key="PosBrandPressed" Color="#166534"/>
|
<SolidColorBrush x:Key="PosBrandPressed" Color="#166534"/>
|
||||||
<SolidColorBrush x:Key="PosBrandText" Color="#FFFFFF"/>
|
<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="PosDanger" Color="#DC2626"/>
|
||||||
<SolidColorBrush x:Key="PosDangerSoft" Color="#FEE2E2"/>
|
<SolidColorBrush x:Key="PosDangerSoft" Color="#FEE2E2"/>
|
||||||
|
<SolidColorBrush x:Key="PosDangerBorder" Color="#F3C4C4"/>
|
||||||
<SolidColorBrush x:Key="PosWarn" Color="#B45309"/>
|
<SolidColorBrush x:Key="PosWarn" Color="#B45309"/>
|
||||||
<SolidColorBrush x:Key="PosWarnSoft" Color="#FEF3C7"/>
|
<SolidColorBrush x:Key="PosWarnSoft" Color="#FEF3C7"/>
|
||||||
<SolidColorBrush x:Key="PosScrim" Color="#33203028"/>
|
<SolidColorBrush x:Key="PosScrim" Color="#6B0F1F17"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
||||||
<!-- ── Тёмная тема (опция, те же токены) ───────────────────────── -->
|
<!-- ── Тёмная тема (опция, те же токены) ───────────────────────── -->
|
||||||
|
|
@ -27,19 +33,25 @@
|
||||||
<SolidColorBrush x:Key="PosBg" Color="#0F1713"/>
|
<SolidColorBrush x:Key="PosBg" Color="#0F1713"/>
|
||||||
<SolidColorBrush x:Key="PosCard" Color="#15201B"/>
|
<SolidColorBrush x:Key="PosCard" Color="#15201B"/>
|
||||||
<SolidColorBrush x:Key="PosSubtle" Color="#1B2821"/>
|
<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="PosBorder" Color="#2A3A31"/>
|
||||||
<SolidColorBrush x:Key="PosText" Color="#E8EFEA"/>
|
<SolidColorBrush x:Key="PosText" Color="#E8EFEA"/>
|
||||||
<SolidColorBrush x:Key="PosTextMuted" Color="#9BAAA2"/>
|
<SolidColorBrush x:Key="PosTextMuted" Color="#9BAAA2"/>
|
||||||
|
<SolidColorBrush x:Key="PosPlaceholder" Color="#7E8E86"/>
|
||||||
<SolidColorBrush x:Key="PosBrand" Color="#22C55E"/>
|
<SolidColorBrush x:Key="PosBrand" Color="#22C55E"/>
|
||||||
|
<SolidColorBrush x:Key="PosBrandDark" Color="#34D37F"/>
|
||||||
<SolidColorBrush x:Key="PosBrandHover" Color="#16A34A"/>
|
<SolidColorBrush x:Key="PosBrandHover" Color="#16A34A"/>
|
||||||
<SolidColorBrush x:Key="PosBrandPressed" Color="#15803D"/>
|
<SolidColorBrush x:Key="PosBrandPressed" Color="#15803D"/>
|
||||||
<SolidColorBrush x:Key="PosBrandText" Color="#06140C"/>
|
<SolidColorBrush x:Key="PosBrandText" Color="#06140C"/>
|
||||||
<SolidColorBrush x:Key="PosBrandSoft" Color="#13331F"/>
|
<SolidColorBrush x:Key="PosBrandSoft" Color="#13331F"/>
|
||||||
|
<SolidColorBrush x:Key="PosSelRow" Color="#132A1D"/>
|
||||||
<SolidColorBrush x:Key="PosDanger" Color="#F87171"/>
|
<SolidColorBrush x:Key="PosDanger" Color="#F87171"/>
|
||||||
<SolidColorBrush x:Key="PosDangerSoft" Color="#3A1A1A"/>
|
<SolidColorBrush x:Key="PosDangerSoft" Color="#3A1A1A"/>
|
||||||
|
<SolidColorBrush x:Key="PosDangerBorder" Color="#5A2A2A"/>
|
||||||
<SolidColorBrush x:Key="PosWarn" Color="#FBBF24"/>
|
<SolidColorBrush x:Key="PosWarn" Color="#FBBF24"/>
|
||||||
<SolidColorBrush x:Key="PosWarnSoft" Color="#33280D"/>
|
<SolidColorBrush x:Key="PosWarnSoft" Color="#33280D"/>
|
||||||
<SolidColorBrush x:Key="PosScrim" Color="#66000000"/>
|
<SolidColorBrush x:Key="PosScrim" Color="#6B000000"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
||||||
</ResourceDictionary.ThemeDictionaries>
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
|
|
||||||
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;
|
vm.CurrentPage = sale;
|
||||||
return new MainWindow { DataContext = vm };
|
return new MainWindow { DataContext = vm };
|
||||||
}
|
}
|
||||||
/// <summary>Модальная оплата на затемнённой подложке (как в оболочке):
|
/// <summary>Модальная оплата поверх затемнённого экрана продажи — как в оболочке
|
||||||
/// светлый фон → полупрозрачная тёмная подложка → белое модальное окно.</summary>
|
/// и как в макете: чек виден под полупрозрачной тёмной подложкой.</summary>
|
||||||
private static Control PayScrim(PaymentViewModel pay)
|
private static Control PayScrim(PaymentViewModel pay)
|
||||||
=> new Border
|
{
|
||||||
{
|
var vm = new MainWindowViewModel(); // в CurrentPage уже лежит пример продажи
|
||||||
Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#F4F6F5")),
|
vm.Payment = pay;
|
||||||
Child = new Border
|
vm.IsPaymentOpen = true;
|
||||||
{
|
return new MainWindow { DataContext = vm };
|
||||||
Background = new Avalonia.Media.SolidColorBrush(Avalonia.Media.Color.Parse("#660F1F17")),
|
}
|
||||||
Child = new PaymentView { DataContext = pay },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Control ShellOffline()
|
private static Control ShellOffline()
|
||||||
{
|
{
|
||||||
|
|
@ -160,7 +157,9 @@ private static void WriteGallery(string dir, List<(string file, string caption)>
|
||||||
foreach (var w in wins)
|
foreach (var w in wins)
|
||||||
{
|
{
|
||||||
var rel = "windows/" + Path.GetFileName(w);
|
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>");
|
sb.Append("</div>");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
[ObservableProperty] private string _cashierName = "—";
|
[ObservableProperty] private string _cashierName = "—";
|
||||||
[ObservableProperty] private string _shiftNumber = "смена не открыта";
|
[ObservableProperty] private string _shiftNumber = "смена не открыта";
|
||||||
[ObservableProperty] private bool _isShiftOpen;
|
[ObservableProperty] private bool _isShiftOpen;
|
||||||
[ObservableProperty] private string _pointName = "Магазин";
|
[ObservableProperty] private string _storeName = "Магазин";
|
||||||
|
[ObservableProperty] private string _pointName = "Касса";
|
||||||
[ObservableProperty] private bool _isOnline = true;
|
[ObservableProperty] private bool _isOnline = true;
|
||||||
[ObservableProperty] private int _queueCount;
|
[ObservableProperty] private int _queueCount;
|
||||||
[ObservableProperty] private PaymentViewModel? _payment;
|
[ObservableProperty] private PaymentViewModel? _payment;
|
||||||
|
|
@ -34,14 +35,14 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||||
|
|
||||||
public string NetBadge => IsOnline ? "На связи" : "Нет связи";
|
public string NetBadge => IsOnline ? "На связи" : "Нет связи";
|
||||||
public string ShiftBadge => IsShiftOpen ? $"Смена {ShiftNumber}" : "Смена закрыта";
|
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 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));
|
||||||
|
|
||||||
/// <summary>Конструктор для дизайнера/скриншотов (демо-данные).</summary>
|
/// <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(); }
|
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;
|
public bool HasDiscount => Discount > 0m;
|
||||||
/// <summary>Пояснение скидки в строке, чтобы цена×кол-во ≠ сумма не выглядело
|
/// <summary>Подпись цены за единицу: «280 ₸ за шт», «690 ₸ за кг».</summary>
|
||||||
/// ошибкой: «1 560 → 1 500 ₸, скидка 60».</summary>
|
public string UnitPriceText => $"{Price:N0} ₸ за {Unit}";
|
||||||
public string DiscountText => $"{Gross:N0} → {LineTotal:N0} ₸, скидка {Discount:N0}";
|
/// <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)
|
partial void OnQuantityChanged(decimal value)
|
||||||
{ OnPropertyChanged(nameof(LineTotal)); OnPropertyChanged(nameof(QtyText)); OnPropertyChanged(nameof(Gross)); OnPropertyChanged(nameof(DiscountText)); }
|
{ OnPropertyChanged(nameof(LineTotal)); OnPropertyChanged(nameof(QtyText)); OnPropertyChanged(nameof(Gross)); OnPropertyChanged(nameof(DiscountText)); }
|
||||||
partial void OnDiscountChanged(decimal value)
|
partial void OnDiscountChanged(decimal value)
|
||||||
|
|
@ -396,6 +398,10 @@ public partial class SaleViewModel : ViewModelBase
|
||||||
[ObservableProperty] private SaleLineVm? _selectedLine;
|
[ObservableProperty] private SaleLineVm? _selectedLine;
|
||||||
public bool IsCartEmpty => ItemCount == 0;
|
public bool IsCartEmpty => ItemCount == 0;
|
||||||
public bool HasAnyDiscount => Discount > 0m;
|
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 OnItemCountChanged(int value) => OnPropertyChanged(nameof(IsCartEmpty));
|
||||||
partial void OnDiscountChanged(decimal value) => OnPropertyChanged(nameof(HasAnyDiscount));
|
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 = "Чек очищен"; }
|
[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>
|
/// <summary>Отложить текущий чек и начать новый (обслужить другого покупателя).</summary>
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
@ -567,12 +575,15 @@ public static SaleViewModel Sample()
|
||||||
vm.Total = decimal.Round(vm.Subtotal - vm.Discount, 2);
|
vm.Total = decimal.Round(vm.Subtotal - vm.Discount, 2);
|
||||||
vm.ItemCount = vm.Lines.Count;
|
vm.ItemCount = vm.Lines.Count;
|
||||||
vm.Message = "Чек готов к оплате — F12 Оплата";
|
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()
|
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 };
|
{ 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[] {
|
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("Яблоки (вес)", "кг", 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);
|
vm.Favorites.Add(f);
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,63 +7,61 @@
|
||||||
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
<Panel>
|
<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">
|
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
<StackPanel Orientation="Horizontal" Spacing="24" VerticalAlignment="Center">
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
|
<TextBlock VerticalAlignment="Center" FontSize="20">
|
||||||
<Border Background="{DynamicResource PosBrand}" CornerRadius="9" Width="34" Height="34">
|
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||||
<TextBlock Text="FM" Foreground="White" FontWeight="Bold" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
</TextBlock>
|
||||||
</Border>
|
<Border Width="1" Height="28" Background="{DynamicResource PosBorder}"/>
|
||||||
<StackPanel VerticalAlignment="Center">
|
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||||||
<TextBlock Text="Food Market · Касса" Foreground="{DynamicResource PosText}" FontWeight="Bold" FontSize="16"/>
|
<Run Text="Магазин "/><Run Text="{Binding StoreName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/><Run Text=" · Точка "/><Run Text="{Binding PointName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||||
<TextBlock Text="{Binding PointName}" Foreground="{DynamicResource PosTextMuted}" FontSize="12.5"/>
|
</TextBlock>
|
||||||
</StackPanel>
|
<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>
|
||||||
|
<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}">
|
<Border Classes="chip" Classes.ok="{Binding IsShiftOpen}" Classes.warn="{Binding !IsShiftOpen}">
|
||||||
<TextBlock Text="{Binding ShiftBadge}"/>
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||||||
|
<TextBlock Text="{Binding ShiftBadge}"/>
|
||||||
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Classes="chip" Classes.ok="{Binding IsOnline}" Classes.warn="{Binding !IsOnline}">
|
<Border Classes="chip net">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||||||
<TextBlock Text="{Binding NetBadge}"/>
|
<TextBlock Text="{Binding NetBadge}"/>
|
||||||
<TextBlock Text="{Binding QueueBadge}" IsVisible="{Binding QueueBadge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
<TextBlock Text="{Binding QueueBadge}" IsVisible="{Binding QueueBadge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</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>
|
</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>
|
</Border>
|
||||||
|
|
||||||
<!-- Текущая страница -->
|
<!-- Текущая страница -->
|
||||||
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}"/>
|
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}"/>
|
||||||
|
|
||||||
<!-- Горячие клавиши -->
|
<!-- Полоса горячих клавиш -->
|
||||||
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0">
|
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="24,0">
|
||||||
<ItemsControl VerticalAlignment="Center" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Spacing="28" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="8"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F1"/></Border><TextBlock Text="Помощь" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F2"/></Border><TextBlock Text="Поиск" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F4"/></Border><TextBlock Text="Скидка" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F7"/></Border><TextBlock Text="Отложить" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F9"/></Border><TextBlock Text="Возврат" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F10"/></Border><TextBlock Text="Смена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F12"/></Border><TextBlock Text="Оплата" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Del"/></Border><TextBlock Text="Удалить строку" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
<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>
|
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Esc"/></Border><TextBlock Text="Отмена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||||||
</ItemsControl>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,76 +3,105 @@
|
||||||
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
||||||
x:Class="foodmarket.Pos.Views.PaymentView"
|
x:Class="foodmarket.Pos.Views.PaymentView"
|
||||||
x:DataType="vm:PaymentViewModel">
|
x:DataType="vm:PaymentViewModel">
|
||||||
<Border Classes="card" Width="720" Padding="28" CornerRadius="18" VerticalAlignment="Center" HorizontalAlignment="Center">
|
<Border Width="880" Background="{DynamicResource PosCard}" CornerRadius="20" Padding="32" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
<StackPanel Spacing="16">
|
BoxShadow="0 24 64 0 #40000000">
|
||||||
|
<StackPanel Spacing="24">
|
||||||
|
<!-- Заголовок -->
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<TextBlock Text="Оплата" FontSize="22" FontWeight="Bold" Foreground="{DynamicResource PosText}" VerticalAlignment="Center"/>
|
<TextBlock Text="Оплата" FontSize="24" FontWeight="Bold" Foreground="{DynamicResource PosText}" VerticalAlignment="Bottom"/>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" VerticalAlignment="Bottom">
|
||||||
<TextBlock Text="К оплате" Foreground="{DynamicResource PosTextMuted}" FontSize="16" VerticalAlignment="Center"/>
|
<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="30" FontWeight="Bold"/>
|
<TextBlock Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="40" FontWeight="ExtraBold"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Сегменты способа оплаты (слитный переключатель) -->
|
<!-- Сегменты способа оплаты (в серой подложке) -->
|
||||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="12" Padding="4">
|
<Border Background="{DynamicResource PosChipNet}" CornerRadius="12" Padding="4">
|
||||||
<UniformGrid Columns="3">
|
<UniformGrid Columns="3">
|
||||||
<Button Classes="pos seg" Classes.on="{Binding IsCash}" Margin="2" Content="Наличные" Command="{Binding SelectCashCommand}"/>
|
<Button Classes="pos seg" Classes.on="{Binding IsCash}" Margin="2" Command="{Binding SelectCashCommand}">
|
||||||
<Button Classes="pos seg" Classes.on="{Binding IsCard}" Margin="2" Content="Карта" Command="{Binding SelectCardCommand}"/>
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
<Button Classes="pos seg" Classes.on="{Binding IsMixed}" Margin="2" Content="Смешанная" Command="{Binding SelectMixedCommand}"/>
|
<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>
|
</UniformGrid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<!-- Тело: поле+быстрые суммы | клавиатура -->
|
||||||
<Grid ColumnDefinitions="*,300">
|
<Grid ColumnDefinitions="*,300">
|
||||||
<!-- Левая часть: суммы -->
|
<StackPanel Grid.Column="0" Margin="0,0,24,0">
|
||||||
<StackPanel Grid.Column="0" Spacing="12" Margin="0,0,20,0">
|
<TextBlock Text="Получено наличными" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||||
<StackPanel Spacing="4">
|
<Border Height="64" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBrand}" BorderThickness="2" CornerRadius="12" Padding="20,0">
|
||||||
<TextBlock Text="Получено наличными" Foreground="{DynamicResource PosTextMuted}" FontSize="14"/>
|
<TextBlock Text="{Binding PaidCash, StringFormat='{}{0:N0} ₸'}" FontSize="30" FontWeight="Bold" Foreground="{DynamicResource PosText}" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||||
<TextBox Classes="pos" FontSize="24" Text="{Binding PaidCash}"/>
|
</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>
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding IsMixed}">
|
|
||||||
<TextBlock Text="Картой" Foreground="{DynamicResource PosTextMuted}" FontSize="14"/>
|
<!-- Быстрые суммы 3×2 -->
|
||||||
<TextBox Classes="pos" FontSize="24" Text="{Binding PaidCard}"/>
|
<UniformGrid Columns="3" Margin="0,14,0,0" IsVisible="{Binding !IsCard}">
|
||||||
</StackPanel>
|
<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"/>
|
||||||
<UniformGrid Columns="3" IsVisible="{Binding !IsCard}">
|
<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" Margin="3" FontSize="14.5" Padding="4,0" Content="Без сдачи" Command="{Binding QuickCashCommand}" CommandParameter="exact"/>
|
<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" Margin="3" Content="1 000" Command="{Binding QuickCashCommand}" CommandParameter="1000"/>
|
<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" Margin="3" Content="2 000" Command="{Binding QuickCashCommand}" CommandParameter="2000"/>
|
<Button Classes="pos ghost" FontSize="16" Height="52" Margin="5,0,0,0" Content="Своя сумма" Command="{Binding KeyClearCommand}"/>
|
||||||
<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"/>
|
|
||||||
</UniformGrid>
|
</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">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<TextBlock Text="СДАЧА" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
<TextBlock Text="СДАЧА" Foreground="{DynamicResource PosBrandDark}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Change, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="36" FontWeight="Bold"/>
|
<TextBlock Grid.Column="1" Text="{Binding Change, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrandDark}" FontSize="44" FontWeight="ExtraBold" VerticalAlignment="Center"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Правая часть: цифровая клавиатура 3×4 (ровные квадраты) -->
|
<!-- Клавиатура 3×4 одинаковых 72px -->
|
||||||
<UniformGrid Grid.Column="1" Columns="3" Rows="4" VerticalAlignment="Top" IsVisible="{Binding !IsCard}">
|
<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="5" 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="5" 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="5" 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="5" 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="5" 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="5" 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="5" 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="5" 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="5" 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="5" Height="72" FontSize="16" Foreground="{DynamicResource PosTextMuted}" Content="Сброс" Command="{Binding KeyClearCommand}"/>
|
||||||
<Button Classes="key" Margin="4" Height="72" Content="0" Command="{Binding KeyDigitCommand}" CommandParameter="0"/>
|
<Button Classes="key" Margin="5" 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" FontSize="20" Foreground="{DynamicResource PosTextMuted}" Content="⌫" Command="{Binding KeyBackCommand}"/>
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="66" FontSize="20" Command="{Binding ConfirmCommand}" IsDefault="True" IsEnabled="{Binding Sufficient}">
|
<!-- Действия: Назад 200px | Провести 1fr -->
|
||||||
<TextBlock Text="Провести и печатать · Enter" TextAlignment="Center"/>
|
<Grid ColumnDefinitions="200,*">
|
||||||
</Button>
|
<Button Classes="pos ghost" Height="68" Margin="0,0,12,0" Command="{Binding CancelCommand}">
|
||||||
<Button Classes="pos ghost" HorizontalAlignment="Stretch" Height="44" Content="Esc · Назад" 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 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>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -3,93 +3,107 @@
|
||||||
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
||||||
x:Class="foodmarket.Pos.Views.SaleView"
|
x:Class="foodmarket.Pos.Views.SaleView"
|
||||||
x:DataType="vm:SaleViewModel">
|
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">
|
<Border Grid.Row="0" Height="64" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBrand}" BorderThickness="2" CornerRadius="14"
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
BoxShadow="0 0 0 4 #1F16A34A" Margin="0,0,0,16">
|
||||||
<PathIcon Grid.Column="0" Width="22" Height="22" Margin="18,0,8,0" Foreground="{DynamicResource PosTextMuted}"
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
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"/>
|
<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"
|
<TextBox Grid.Column="1" BorderThickness="0" FontSize="19" Background="Transparent" VerticalContentAlignment="Center"
|
||||||
Watermark="Отсканируйте товар или начните вводить название" Text="{Binding ScanInput}">
|
Watermark="Отсканируйте товар или начните вводить название" Text="{Binding ScanInput}">
|
||||||
<TextBox.KeyBindings><KeyBinding Gesture="Enter" Command="{Binding ScanCommand}"/></TextBox.KeyBindings>
|
<TextBox.KeyBindings><KeyBinding Gesture="Enter" Command="{Binding ScanCommand}"/></TextBox.KeyBindings>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
|
<Border Grid.Column="2" Classes="kbd" Margin="0,0,16,0"><TextBlock Text="F2"/></Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Избранное (быстрые товары) -->
|
<!-- Заголовок избранного -->
|
||||||
<StackPanel Grid.Row="1" Spacing="8" Margin="0,0,0,14" IsVisible="{Binding HasFavorites}">
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="0,0,0,12" IsVisible="{Binding HasFavorites}">
|
||||||
<Button Classes="pos ghost" HorizontalAlignment="Left" Height="32" Padding="12,0" FontSize="13.5" Command="{Binding ToggleFavoritesCommand}" BorderThickness="0" Background="Transparent">
|
<TextBlock Text="ИЗБРАННОЕ" FontSize="15" FontWeight="Bold" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<Button Grid.Column="1" Background="Transparent" BorderThickness="0" Padding="0" Command="{Binding ToggleFavoritesCommand}" HorizontalAlignment="Right">
|
||||||
<PathIcon Width="12" Height="12" Foreground="{DynamicResource PosTextMuted}" IsVisible="{Binding FavoritesOpen}" Data="M7 10l5 5 5-5z"/>
|
<Panel>
|
||||||
<PathIcon Width="12" Height="12" Foreground="{DynamicResource PosTextMuted}" IsVisible="{Binding !FavoritesOpen}" Data="M10 17l5-5-5-5z"/>
|
<TextBlock FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource PosBrandDark}" Text="Скрыть ▴" IsVisible="{Binding FavoritesOpen}"/>
|
||||||
<TextBlock Text="Избранное" Foreground="{DynamicResource PosTextMuted}" FontWeight="Medium"/>
|
<TextBlock FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource PosBrandDark}" Text="Показать ▾" IsVisible="{Binding !FavoritesOpen}"/>
|
||||||
</StackPanel>
|
</Panel>
|
||||||
</Button>
|
</Button>
|
||||||
<ItemsControl ItemsSource="{Binding Favorites}" IsVisible="{Binding FavoritesOpen}">
|
</Grid>
|
||||||
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="3"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
<!-- Избранное -->
|
||||||
<DataTemplate>
|
<ItemsControl Grid.Row="2" ItemsSource="{Binding Favorites}" Margin="0,0,0,16" IsVisible="{Binding FavoritesOpen}">
|
||||||
<Button Classes="pos ghost" Height="72" Margin="4" Padding="6,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
|
<ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Columns="6"/></ItemsPanelTemplate></ItemsControl.ItemsPanel>
|
||||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).AddFavoriteCommand}" CommandParameter="{Binding}">
|
<ItemsControl.ItemTemplate>
|
||||||
<StackPanel Spacing="2" HorizontalAlignment="Stretch">
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" FontSize="14" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" HorizontalAlignment="Stretch" Foreground="{DynamicResource PosText}"/>
|
<Button Height="84" Margin="6" Padding="4" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="12"
|
||||||
<TextBlock Text="{Binding Price, StringFormat='{}{0:N0} ₸'}" FontSize="13" TextAlignment="Center" HorizontalAlignment="Stretch" Foreground="{DynamicResource PosBrand}"/>
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
|
||||||
</StackPanel>
|
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).AddFavoriteCommand}" CommandParameter="{Binding}">
|
||||||
</Button>
|
<StackPanel Spacing="6" HorizontalAlignment="Stretch">
|
||||||
</DataTemplate>
|
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="SemiBold" TextAlignment="Center" TextWrapping="Wrap" Foreground="{DynamicResource PosText}"/>
|
||||||
</ItemsControl.ItemTemplate>
|
<TextBlock Text="{Binding PriceText}" FontSize="15" FontWeight="Bold" TextAlignment="Center" Foreground="{DynamicResource PosBrandDark}"/>
|
||||||
</ItemsControl>
|
</StackPanel>
|
||||||
</StackPanel>
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
<!-- Корзина -->
|
<!-- Корзина -->
|
||||||
<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 RowDefinitions="Auto,*">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,180,120,52" Height="40" Margin="18,6,10,0">
|
<Border Grid.Row="0" Height="48" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1">
|
||||||
<TextBlock Text="Товар" Foreground="{DynamicResource PosTextMuted}" FontSize="13" VerticalAlignment="Center"/>
|
<Grid ColumnDefinitions="*,200,160,56" Margin="20,0">
|
||||||
<TextBlock Grid.Column="1" Text="Количество" Foreground="{DynamicResource PosTextMuted}" FontSize="13" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
<TextBlock Text="Товар" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Column="2" Text="Сумма" Foreground="{DynamicResource PosTextMuted}" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
<TextBlock Grid.Column="1" Text="Количество" Foreground="{DynamicResource PosTextMuted}" FontSize="14" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
</Grid>
|
<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}">
|
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10" IsVisible="{Binding IsCartEmpty}">
|
||||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="999" Width="72" Height="72">
|
<Border Background="{DynamicResource PosSubtle}" CornerRadius="999" Width="72" Height="72">
|
||||||
<PathIcon Width="34" Height="34" Foreground="{DynamicResource PosTextMuted}"
|
<Path Width="34" Height="34" Stretch="Uniform" Stroke="{DynamicResource PosTextMuted}" StrokeThickness="1.6" StrokeLineCap="Round" StrokeJoin="Round"
|
||||||
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"/>
|
Data="M6 6h15l-1.5 9h-12zM6 6l-2 -3M9 20a1 1 0 100 .01M18 20a1 1 0 100 .01"/>
|
||||||
</Border>
|
</Border>
|
||||||
<TextBlock Text="Отсканируйте первый товар" Foreground="{DynamicResource PosTextMuted}" FontSize="16"/>
|
<TextBlock Text="Отсканируйте первый товар" Foreground="{DynamicResource PosTextMuted}" FontSize="16"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
|
<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>
|
<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.Styles>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:SaleLineVm">
|
<DataTemplate x:DataType="vm:SaleLineVm">
|
||||||
<Border Background="{DynamicResource PosSubtle}" CornerRadius="10" Padding="12,8">
|
<Border Classes="row" Height="76" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1">
|
||||||
<Grid ColumnDefinitions="*,180,120,52">
|
<Grid ColumnDefinitions="4,*,200,160,56">
|
||||||
<StackPanel VerticalAlignment="Center">
|
<Rectangle Grid.Column="0" Classes="accent" Fill="{DynamicResource PosBrand}"/>
|
||||||
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource PosText}" FontSize="17" FontWeight="Medium" TextWrapping="Wrap"/>
|
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="16,0,0,0">
|
||||||
<TextBlock Text="{Binding DiscountText}" Foreground="{DynamicResource PosBrand}" FontSize="13" IsVisible="{Binding HasDiscount}"/>
|
<TextBlock Text="{Binding Name}" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" TextTrimming="CharacterEllipsis"/>
|
||||||
<TextBlock Text="{Binding Price, StringFormat='{}{0:N0} ₸ / ед.'}" Foreground="{DynamicResource PosTextMuted}" FontSize="13" IsVisible="{Binding !HasDiscount}"/>
|
<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>
|
</StackPanel>
|
||||||
<!-- Степпер 44×44 -->
|
<Border Grid.Column="2" Height="44" Margin="16,0" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="10" ClipToBounds="True">
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" HorizontalAlignment="Center" VerticalAlignment="Center">
|
<Grid ColumnDefinitions="44,*,44">
|
||||||
<Button Classes="key" Width="44" Height="44" FontSize="22" Content="−"
|
<Button Classes="stepbtn" Content="−" Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).DecLineCommand}" CommandParameter="{Binding}"/>
|
||||||
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"/>
|
||||||
<TextBlock Text="{Binding QtyText}" Foreground="{DynamicResource PosText}" FontSize="16" FontWeight="SemiBold" MinWidth="56" TextAlignment="Center" VerticalAlignment="Center"/>
|
<Button Grid.Column="2" Classes="stepbtn" FontSize="20" Content="+" Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).IncLineCommand}" CommandParameter="{Binding}"/>
|
||||||
<Button Classes="key" Width="44" Height="44" FontSize="20" Content="+"
|
</Grid>
|
||||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).IncLineCommand}" CommandParameter="{Binding}"/>
|
</Border>
|
||||||
</StackPanel>
|
<TextBlock Grid.Column="3" Text="{Binding LineTotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||||
<TextBlock Grid.Column="2" Text="{Binding LineTotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="19" FontWeight="SemiBold" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
<Button Grid.Column="4" Width="44" Height="44" Background="Transparent" BorderThickness="0" 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"
|
|
||||||
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).RemoveLineCommand}" CommandParameter="{Binding}" ToolTip.Tip="Удалить">
|
Command="{Binding $parent[ItemsControl].((vm:SaleViewModel)DataContext).RemoveLineCommand}" CommandParameter="{Binding}" ToolTip.Tip="Удалить">
|
||||||
<PathIcon Width="18" Height="18" Foreground="{DynamicResource PosDanger}"
|
<Path Width="20" Height="20" Stretch="Uniform" Stroke="#9AA7A1" StrokeThickness="2" StrokeLineCap="Round" StrokeJoin="Round"
|
||||||
Data="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
Data="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
@ -98,40 +112,85 @@
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Text="{Binding Message}" Foreground="{DynamicResource PosTextMuted}" Margin="4,10,0,0" FontSize="13.5"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- ПРАВО: чек + оплата (компактно, без пустот) -->
|
<!-- ПРАВО -->
|
||||||
<Border Grid.Column="1" Classes="card" Margin="10,18,20,18" Padding="18">
|
<Border Grid.Column="1" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1" CornerRadius="14" Margin="8,16,24,16">
|
||||||
<StackPanel Spacing="12">
|
<Grid RowDefinitions="Auto,*,Auto" Margin="24">
|
||||||
<StackPanel Spacing="8">
|
<StackPanel Grid.Row="0" Spacing="16">
|
||||||
<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="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="15"/><TextBlock Grid.Column="1" Text="{Binding Subtotal, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosText}" FontSize="15"/></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="15"/><TextBlock Grid.Column="1" Text="{Binding Discount, StringFormat='−{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="15" FontWeight="Medium"/></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 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>
|
||||||
|
<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>
|
||||||
|
|
||||||
<Border Background="{DynamicResource PosBrandSoft}" CornerRadius="12" Padding="16,14">
|
<StackPanel Grid.Row="2" Spacing="12">
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Button Classes="pos primary" HorizontalAlignment="Stretch" Height="76" Command="{Binding PayCommand}">
|
||||||
<TextBlock Text="ИТОГО" Foreground="{DynamicResource PosText}" FontSize="18" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:N0} ₸'}" Foreground="{DynamicResource PosBrand}" FontSize="42" FontWeight="Bold"/>
|
<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>
|
</Grid>
|
||||||
</Border>
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
<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}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||