rebrand: касса ALL MARKET (лого, иконка, заголовок, чек)
- Верхняя полоса и экран входа: логотип-изображение ALL MARKET из ресурсов (avares Assets/), color на светлой теме и white на тёмной (Brand.ApplyLogo). - Заголовок окна «ALL MARKET · Касса»; иконка окна и ApplicationIcon = allmarket.ico (собрана из фирменных PNG 32/180/192). - Чек: в шапке — название точки/магазина (резолвится из RetailPoint, PosSession.StoreName), внизу мелко «ALL MARKET — система учёта торговли» (был хардкод «Food Market» в шапке). - AppImage .desktop: Name «ALL MARKET Касса» + фирменная иконка. - Эталон-макет pos-mockup.html — логотип «all market». - AssemblyName/exe (foodmarket.Pos) и имена артефактов не менялись (инфраструктура). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d1176e0fb1
commit
d0d6ebcea1
|
|
@ -42,14 +42,18 @@ build_appimage() {
|
||||||
cp -r "$src/." "$appdir/usr/bin/"
|
cp -r "$src/." "$appdir/usr/bin/"
|
||||||
cat > "$appdir/foodmarket-pos.desktop" <<DESK
|
cat > "$appdir/foodmarket-pos.desktop" <<DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=Food Market Касса
|
Name=ALL MARKET Касса
|
||||||
Exec=foodmarket.Pos
|
Exec=foodmarket.Pos
|
||||||
Icon=foodmarket-pos
|
Icon=foodmarket-pos
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Office;
|
Categories=Office;
|
||||||
DESK
|
DESK
|
||||||
# Минимальная иконка-заглушка (1x1 PNG), если нет фирменной.
|
# Фирменная иконка ALL MARKET (зелёный квадрат 512). Фоллбэк — 1x1 PNG.
|
||||||
|
if [ -f src/food-market.pos/Assets/allmarket-icon-512.png ]; then
|
||||||
|
cp src/food-market.pos/Assets/allmarket-icon-512.png "$appdir/foodmarket-pos.png"
|
||||||
|
else
|
||||||
printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png"
|
printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png"
|
||||||
|
fi
|
||||||
cat > "$appdir/AppRun" <<'RUN'
|
cat > "$appdir/AppRun" <<'RUN'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
HERE="$(dirname "$(readlink -f "$0")")"
|
HERE="$(dirname "$(readlink -f "$0")")"
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
/* верхняя полоса */
|
/* верхняя полоса */
|
||||||
.top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px}
|
.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{font-weight:500;font-size:22px;color:var(--green);letter-spacing:-.5px}
|
||||||
.logo span{color:var(--text);font-weight:600}
|
.logo b{color:#4C4F54;font-weight:300}
|
||||||
|
.logo span{color:var(--muted);font-weight:600;font-size:17px;margin-left:4px}
|
||||||
.sep{width:1px;height:28px;background:var(--line)}
|
.sep{width:1px;height:28px;background:var(--line)}
|
||||||
.meta{color:var(--muted);font-size:15px}
|
.meta{color:var(--muted);font-size:15px}
|
||||||
.meta b{color:var(--text);font-weight:600}
|
.meta b{color:var(--text);font-weight:600}
|
||||||
|
|
@ -125,7 +126,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<header class="top">
|
<header class="top">
|
||||||
<div class="logo">FOOD MARKET <span>· Касса</span></div>
|
<div class="logo">all<b> market</b><span>· Касса</span></div>
|
||||||
<div class="sep"></div>
|
<div class="sep"></div>
|
||||||
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
<div class="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
||||||
<div class="sep"></div>
|
<div class="sep"></div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ public sealed class PosSession
|
||||||
public required string AccessToken { get; set; }
|
public required string AccessToken { get; set; }
|
||||||
public required string RefreshToken { get; set; }
|
public required string RefreshToken { get; set; }
|
||||||
public string? CashierName { get; set; }
|
public string? CashierName { get; set; }
|
||||||
|
/// <summary>Название точки/магазина — для шапки чека (резолвится из RetailPoint).</summary>
|
||||||
|
public string? StoreName { get; set; }
|
||||||
public Guid? CashierUserId { get; set; }
|
public Guid? CashierUserId { get; set; }
|
||||||
public string[]? Roles { get; set; }
|
public string[]? Roles { get; set; }
|
||||||
public Guid? RetailPointId { get; set; }
|
public Guid? RetailPointId { get; set; }
|
||||||
|
|
|
||||||
BIN
src/food-market.pos/Assets/allmarket-icon-512.png
Normal file
BIN
src/food-market.pos/Assets/allmarket-icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
src/food-market.pos/Assets/allmarket-logo-color.png
Normal file
BIN
src/food-market.pos/Assets/allmarket-logo-color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
src/food-market.pos/Assets/allmarket-logo-white.png
Normal file
BIN
src/food-market.pos/Assets/allmarket-logo-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
src/food-market.pos/Assets/allmarket.ico
Normal file
BIN
src/food-market.pos/Assets/allmarket.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -96,7 +96,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
|
||||||
IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard)
|
IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard)
|
||||||
{
|
{
|
||||||
var doc = new ReceiptDoc { Title = "ВОЗВРАТ" };
|
var doc = new ReceiptDoc { Title = "ВОЗВРАТ" };
|
||||||
doc.Add("Food Market", center: true, bold: true);
|
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
|
||||||
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
||||||
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||||
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||||
|
|
@ -117,6 +117,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending
|
||||||
if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}");
|
if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}");
|
||||||
if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
|
if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
|
||||||
doc.Separator();
|
doc.Separator();
|
||||||
|
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +125,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
|
||||||
{
|
{
|
||||||
var t = cart.Totals();
|
var t = cart.Totals();
|
||||||
var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" };
|
var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" };
|
||||||
doc.Add("Food Market", center: true, bold: true);
|
doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true);
|
||||||
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true);
|
||||||
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||||
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"));
|
||||||
|
|
@ -145,6 +146,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
|
||||||
if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}");
|
if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}");
|
||||||
doc.Separator();
|
doc.Separator();
|
||||||
doc.Add("Спасибо за покупку!", center: true);
|
doc.Add("Спасибо за покупку!", center: true);
|
||||||
|
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ private void StartSession()
|
||||||
{
|
{
|
||||||
CashierName = _svc!.Session?.CashierName ?? "кассир";
|
CashierName = _svc!.Session?.CashierName ?? "кассир";
|
||||||
QueueCount = _svc.Store.PendingCount();
|
QueueCount = _svc.Store.PendingCount();
|
||||||
|
_ = ResolvePointNameAsync();
|
||||||
_sale = new SaleViewModel(_svc)
|
_sale = new SaleViewModel(_svc)
|
||||||
{
|
{
|
||||||
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
|
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
|
||||||
|
|
@ -72,6 +73,25 @@ private void StartSession()
|
||||||
_ = CheckForUpdateAsync();
|
_ = CheckForUpdateAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Резолвит название точки продаж (для шапки чека и верхней полосы).</summary>
|
||||||
|
private async Task ResolvePointNameAsync()
|
||||||
|
{
|
||||||
|
var pid = _svc?.Session?.RetailPointId;
|
||||||
|
if (_svc?.Api is null || pid is null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var points = await _svc.Api.RetailPointsAsync();
|
||||||
|
var name = points.items.FirstOrDefault(p => p.id == pid.Value)?.name;
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
PointName = name;
|
||||||
|
StoreName = name;
|
||||||
|
if (_svc.Session is not null) _svc.Session.StoreName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* офлайн — оставляем значения по умолчанию */ }
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CheckForUpdateAsync()
|
private async Task CheckForUpdateAsync()
|
||||||
{
|
{
|
||||||
if (_svc?.Api is null) return;
|
if (_svc?.Api is null) return;
|
||||||
|
|
|
||||||
42
src/food-market.pos/Views/Brand.cs
Normal file
42
src/food-market.pos/Views/Brand.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
|
||||||
|
namespace foodmarket.Pos.Views;
|
||||||
|
|
||||||
|
/// <summary>Логотип и иконка ALL MARKET из ресурсов (avares). Логотип подбирается
|
||||||
|
/// под тему: color на светлой, white на тёмной. Файлы готовые — не перерисовывать.</summary>
|
||||||
|
public static class Brand
|
||||||
|
{
|
||||||
|
private const string Pkg = "avares://foodmarket.Pos/Assets/";
|
||||||
|
|
||||||
|
private static bool IsDark =>
|
||||||
|
Application.Current?.ActualThemeVariant == ThemeVariant.Dark;
|
||||||
|
|
||||||
|
/// <summary>Загрузить логотип (под текущую тему) в переданный Image.</summary>
|
||||||
|
public static void ApplyLogo(Image? target)
|
||||||
|
{
|
||||||
|
if (target is null) return;
|
||||||
|
var file = IsDark ? "allmarket-logo-white.png" : "allmarket-logo-color.png";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var s = AssetLoader.Open(new Uri(Pkg + file));
|
||||||
|
target.Source = new Bitmap(s);
|
||||||
|
}
|
||||||
|
catch { /* ресурс недоступен — оставить пустым */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Поставить иконку окна/приложения.</summary>
|
||||||
|
public static void ApplyWindowIcon(Window window)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var s = AssetLoader.Open(new Uri(Pkg + "allmarket.ico"));
|
||||||
|
window.Icon = new WindowIcon(s);
|
||||||
|
}
|
||||||
|
catch { /* ресурс недоступен */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,12 +6,9 @@
|
||||||
<Grid Background="{DynamicResource PosBg}">
|
<Grid Background="{DynamicResource PosBg}">
|
||||||
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
|
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
|
||||||
<StackPanel Spacing="16">
|
<StackPanel Spacing="16">
|
||||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
<StackPanel Spacing="10" HorizontalAlignment="Center">
|
||||||
<Border Background="{DynamicResource PosBrand}" CornerRadius="12" Width="48" Height="48" HorizontalAlignment="Center">
|
<Image x:Name="BrandLogo" Height="48" HorizontalAlignment="Center" Margin="0,4,0,0"/>
|
||||||
<TextBlock Text="FM" Foreground="White" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
<TextBlock Text="Касса · Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" HorizontalAlignment="Center"/>
|
||||||
</Border>
|
|
||||||
<TextBlock Text="Food Market · Касса" Foreground="{DynamicResource PosText}" FontWeight="Bold" FontSize="18" HorizontalAlignment="Center" Margin="0,8,0,0"/>
|
|
||||||
<TextBlock Text="Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" HorizontalAlignment="Center"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- PIN -->
|
<!-- PIN -->
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,9 @@ namespace foodmarket.Pos.Views;
|
||||||
|
|
||||||
public partial class LoginView : UserControl
|
public partial class LoginView : UserControl
|
||||||
{
|
{
|
||||||
public LoginView() => InitializeComponent();
|
public LoginView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
x:Class="foodmarket.Pos.Views.MainWindow"
|
x:Class="foodmarket.Pos.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
||||||
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
<Panel>
|
<Panel>
|
||||||
<Grid RowDefinitions="64,*,40">
|
<Grid RowDefinitions="64,*,40">
|
||||||
|
|
@ -12,10 +12,11 @@
|
||||||
<!-- Верхняя полоса -->
|
<!-- Верхняя полоса -->
|
||||||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
||||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||||||
<!-- Лого (всегда) -->
|
<!-- Лого ALL MARKET (готовый файл; color на светлой теме, white на тёмной) -->
|
||||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="20">
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
|
||||||
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
<Image x:Name="BrandLogo" Height="38" VerticalAlignment="Center"/>
|
||||||
</TextBlock>
|
<TextBlock Text="· Касса" FontSize="18" Foreground="{DynamicResource PosTextMuted}" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Мета (сворачивается по ширине) -->
|
<!-- Мета (сворачивается по ширине) -->
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
||||||
|
Brand.ApplyWindowIcon(this);
|
||||||
|
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
|
||||||
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
||||||
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
||||||
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,14 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<ApplicationIcon></ApplicationIcon>
|
<ApplicationIcon>Assets\allmarket.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Логотипы/иконка ALL MARKET для окна и экранов (avares://foodmarket.Pos/Assets/...) -->
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
|
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
|
||||||
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />
|
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue