feat(web-client-tpos): add receipt print with thermal 80mm layout

- Create pos-helpers.js with printPosReceipt JS function
- Add PrintReceipt method to CafeDesktop.razor (thermal receipt HTML)
- Save receipt items/payment data before cart reset
- Register pos-helpers.js in index.html
- Inject IJSRuntime for print popup
This commit is contained in:
Ho Ngoc Hai
2026-03-03 20:02:15 +07:00
parent e74527dc8f
commit 7562fc1e1f
3 changed files with 87 additions and 1 deletions

View File

@@ -10,6 +10,7 @@
@using WebClientTpos.Client.Services
@inject AuthService AuthService
@inject PosDataService DataService
@inject IJSRuntime JS
@* ═══════════════ MAIN CONTENT AREA ═══════════════ *@
<div class="pos-content-area">
@@ -224,7 +225,8 @@
<div style="font-size:14px;color:var(--pos-text-tertiary);">@FormatPrice(_lastOrderTotal)</div>
<div style="font-size:12px;color:var(--pos-text-tertiary);">Mã: @_lastTransactionId</div>
<div style="display:flex;gap:10px;margin-top:8px;">
<button style="padding:10px 20px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:13px;font-weight:600;">
<button style="padding:10px 20px;border-radius:var(--pos-radius);border:1px solid var(--pos-border-default);background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:13px;font-weight:600;"
@onclick="PrintReceipt">
<i data-lucide="printer" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"></i>In hóa đơn
</button>
<button style="padding:10px 20px;border-radius:var(--pos-radius);border:none;background:var(--pos-orange-primary);color:#fff;cursor:pointer;font-size:13px;font-weight:600;"
@@ -514,6 +516,8 @@
private string _customAmountInput = "";
private decimal _lastOrderTotal;
private string _lastTransactionId = "";
private string _lastPaymentMethod = "";
private List<(string Name, int Qty, decimal Price)> _lastReceiptItems = new();
private decimal ChangeAmount => _receivedAmount - CartTotal;
private void StartPayment()
@@ -581,6 +585,8 @@
StateHasChanged();
_lastOrderTotal = CartTotal;
_lastPaymentMethod = _selectedMethod;
_lastReceiptItems = _cartItems.Select(i => (i.Name, i.Qty, i.Price)).ToList();
var methodLabel = _selectedMethod switch { "cash" => "Tiền mặt", "card" => "Thẻ", "qr" => "QR Code", _ => "Chuyển khoản" };
// EN: Call API to create real order in DB
@@ -633,6 +639,58 @@
_customAmountInput = "";
}
/// <summary>
/// EN: Print receipt — opens a new window with thermal-style receipt and triggers print dialog.
/// VI: In hóa đơn — mở cửa sổ mới với hóa đơn kiểu máy in nhiệt và kích hoạt in.
/// </summary>
private async Task PrintReceipt()
{
var payLabel = _lastPaymentMethod switch { "cash" => "Tiền mặt", "card" => "Thẻ", "qr" => "QR Code", _ => "Chuyển khoản" };
var now = DateTime.Now;
// EN: Build item rows HTML / VI: Tạo HTML hàng sản phẩm
var sb = new System.Text.StringBuilder();
foreach (var item in _lastReceiptItems)
{
sb.AppendLine($"<tr><td style='text-align:left;padding:3px 0;'>{System.Net.WebUtility.HtmlEncode(item.Name)}</td>");
sb.AppendLine($"<td style='text-align:center;padding:3px 4px;'>{item.Qty}</td>");
sb.AppendLine($"<td style='text-align:right;padding:3px 0;'>{item.Price:N0}</td>");
sb.AppendLine($"<td style='text-align:right;padding:3px 0;font-weight:600;'>{item.Qty * item.Price:N0}</td></tr>");
}
var receiptHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>" +
$"<title>Hóa đơn - {_lastTransactionId}</title>" +
"<style>" +
"@page { margin: 4mm; size: 80mm auto; }" +
"body { font-family: 'Courier New', monospace; font-size: 12px; width: 72mm; margin: 0 auto; color: #000; }" +
".c { text-align: center; } .b { font-weight: bold; }" +
".d { border-top: 1px dashed #000; margin: 6px 0; }" +
"table { width: 100%; border-collapse: collapse; }" +
"th { text-align: left; font-size: 11px; border-bottom: 1px solid #000; padding: 2px 0; }" +
".f { font-size: 10px; text-align: center; margin-top: 8px; color: #555; }" +
"</style></head><body>" +
"<div class='c b' style='font-size:16px;'>GoodGo POS</div>" +
"<div class='c' style='font-size:10px;margin-bottom:4px;'>Hệ thống quản lý bán hàng thông minh</div>" +
"<div class='d'></div>" +
$"<div><b>Mã đơn:</b> {_lastTransactionId}</div>" +
$"<div><b>Ngày:</b> {now:dd/MM/yyyy} — {now:HH:mm:ss}</div>" +
$"<div><b>Thanh toán:</b> {payLabel}</div>" +
"<div class='d'></div>" +
"<table><tr><th>Sản phẩm</th><th style='text-align:center;'>SL</th><th style='text-align:right;'>Đ.Giá</th><th style='text-align:right;'>T.Tiền</th></tr>" +
sb.ToString() +
"</table><div class='d'></div>" +
$"<div style='display:flex;justify-content:space-between;font-size:14px;font-weight:bold;'><span>TỔNG CỘNG</span><span>{_lastOrderTotal:N0}₫</span></div>" +
"<div class='d'></div>" +
"<div class='f'>Cảm ơn quý khách! Hẹn gặp lại</div>" +
"<div class='f'>Powered by GoodGo Platform</div>" +
"<script>window.onload=function(){window.print();window.onafterprint=function(){window.close();}}</script>" +
"</body></html>";
// EN: Open popup window and write receipt HTML / VI: Mở popup và ghi HTML hóa đơn
// EN: Call JS helper to open print window / VI: Gọi JS helper mở cửa sổ in
await JS.InvokeVoidAsync("printPosReceipt", receiptHtml);
}
// ═══════════════ HISTORY TAB — API-driven ═══════════════
private string _historySearch = "";
private string _historyFilter = "today";

View File

@@ -105,6 +105,10 @@
<!-- EN: MudBlazor JavaScript -->
<!-- VI: JavaScript MudBlazor -->
<script src="/_content/MudBlazor/MudBlazor.min.js"></script>
<!-- EN: POS helpers (receipt printing, etc.) -->
<!-- VI: Hàm hỗ trợ POS (in hóa đơn, v.v.) -->
<script src="/js/pos-helpers.js"></script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
/**
* EN: POS helper functions for Blazor JSInterop.
* VI: Các hàm hỗ trợ POS cho Blazor JSInterop.
*/
/**
* EN: Print POS receipt — opens new window with receipt HTML and triggers print dialog.
* VI: In hóa đơn POS — mở cửa sổ mới với HTML hóa đơn và kích hoạt in.
* @param {string} receiptHtml - Full HTML content of the receipt
*/
window.printPosReceipt = function (receiptHtml) {
var w = window.open('', '_blank', 'width=350,height=600');
if (w) {
w.document.write(receiptHtml);
w.document.close();
// Give the browser a moment to render before printing
setTimeout(function () {
w.print();
w.onafterprint = function () { w.close(); };
}, 300);
} else {
alert('Trình duyệt đã chặn popup. Vui lòng cho phép popup để in hóa đơn.');
}
};