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/"
|
||||
cat > "$appdir/foodmarket-pos.desktop" <<DESK
|
||||
[Desktop Entry]
|
||||
Name=Food Market Касса
|
||||
Name=ALL MARKET Касса
|
||||
Exec=foodmarket.Pos
|
||||
Icon=foodmarket-pos
|
||||
Type=Application
|
||||
Categories=Office;
|
||||
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"
|
||||
fi
|
||||
cat > "$appdir/AppRun" <<'RUN'
|
||||
#!/bin/sh
|
||||
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}
|
||||
.logo{font-weight:800;font-size:20px;color:var(--green);letter-spacing:.2px}
|
||||
.logo span{color:var(--text);font-weight:600}
|
||||
.logo{font-weight:500;font-size:22px;color:var(--green);letter-spacing:-.5px}
|
||||
.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)}
|
||||
.meta{color:var(--muted);font-size:15px}
|
||||
.meta b{color:var(--text);font-weight:600}
|
||||
|
|
@ -125,7 +126,7 @@
|
|||
<body>
|
||||
<div class="app">
|
||||
<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="meta">Магазин <b>Демо-магазин</b> · Точка <b>Касса 1</b></div>
|
||||
<div class="sep"></div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ public sealed class PosSession
|
|||
public required string AccessToken { get; set; }
|
||||
public required string RefreshToken { get; set; }
|
||||
public string? CashierName { get; set; }
|
||||
/// <summary>Название точки/магазина — для шапки чека (резолвится из RetailPoint).</summary>
|
||||
public string? StoreName { get; set; }
|
||||
public Guid? CashierUserId { get; set; }
|
||||
public string[]? Roles { 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)
|
||||
{
|
||||
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 (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||
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 (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}");
|
||||
doc.Separator();
|
||||
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d
|
|||
{
|
||||
var t = cart.Totals();
|
||||
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 (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}");
|
||||
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}");
|
||||
doc.Separator();
|
||||
doc.Add("Спасибо за покупку!", center: true);
|
||||
doc.Add("ALL MARKET — система учёта торговли", center: true);
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ private void StartSession()
|
|||
{
|
||||
CashierName = _svc!.Session?.CashierName ?? "кассир";
|
||||
QueueCount = _svc.Store.PendingCount();
|
||||
_ = ResolvePointNameAsync();
|
||||
_sale = new SaleViewModel(_svc)
|
||||
{
|
||||
PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(),
|
||||
|
|
@ -72,6 +73,25 @@ private void StartSession()
|
|||
_ = 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()
|
||||
{
|
||||
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}">
|
||||
<Border Classes="card" Width="440" Padding="28" VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="18">
|
||||
<StackPanel Spacing="16">
|
||||
<StackPanel Spacing="4" HorizontalAlignment="Center">
|
||||
<Border Background="{DynamicResource PosBrand}" CornerRadius="12" Width="48" Height="48" HorizontalAlignment="Center">
|
||||
<TextBlock Text="FM" Foreground="White" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="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 Spacing="10" HorizontalAlignment="Center">
|
||||
<Image x:Name="BrandLogo" Height="48" HorizontalAlignment="Center" Margin="0,4,0,0"/>
|
||||
<TextBlock Text="Касса · Вход кассира" Foreground="{DynamicResource PosTextMuted}" FontSize="14" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- PIN -->
|
||||
|
|
|
|||
|
|
@ -4,5 +4,9 @@ namespace foodmarket.Pos.Views;
|
|||
|
||||
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:DataType="vm:MainWindowViewModel"
|
||||
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
||||
Background="{DynamicResource PosBg}" Title="Касса food-market"
|
||||
Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Panel>
|
||||
<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">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||||
<!-- Лого (всегда) -->
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontSize="20">
|
||||
<Run Text="FOOD MARKET" Foreground="{DynamicResource PosBrand}" FontWeight="ExtraBold"/><Run Text=" · Касса" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||||
</TextBlock>
|
||||
<!-- Лого ALL MARKET (готовый файл; color на светлой теме, white на тёмной) -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="12" VerticalAlignment="Center">
|
||||
<Image x:Name="BrandLogo" Height="38" VerticalAlignment="Center"/>
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ public MainWindow()
|
|||
{
|
||||
InitializeComponent();
|
||||
AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel);
|
||||
Brand.ApplyWindowIcon(this);
|
||||
Brand.ApplyLogo(this.FindControl<Image>("BrandLogo"));
|
||||
// Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и
|
||||
// прячем «Магазин …», но чипы статуса никогда не обрезаем.
|
||||
this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity()));
|
||||
|
|
|
|||
|
|
@ -7,9 +7,14 @@
|
|||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<ApplicationIcon></ApplicationIcon>
|
||||
<ApplicationIcon>Assets\allmarket.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Логотипы/иконка ALL MARKET для окна и экранов (avares://foodmarket.Pos/Assets/...) -->
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\food-market.pos.core\food-market.pos.core.csproj" />
|
||||
<ProjectReference Include="..\food-market.shared\food-market.shared.csproj" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue