refactor(csv-export): streamline CSV export logic and remove redundant methods
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m36s

- Replaced manual CSV string construction with CsvExportHelper for better readability and maintainability across DiscountOrdersMainPage, SalesReports, OrderSalesReports, and UserOrderMainPage.
- Removed obsolete EscapeCsv methods to simplify the codebase.
- Enhanced CSV export functionality to utilize a unified base64 conversion method for file saving.
This commit is contained in:
masoodafar-web
2026-08-07 18:40:00 +03:30
parent 1d78633e17
commit 6a9d142dc6
5 changed files with 109 additions and 81 deletions
@@ -0,0 +1,57 @@
using System.Globalization;
using System.Text;
namespace BackOffice.Common.Utilities;
/// <summary>
/// خروجی CSV سازگار با Excel (UTF-8 BOM + sep=, + اعداد بدون جداکننده هزارگان).
/// </summary>
public static class CsvExportHelper
{
public const char Separator = ',';
public static string Escape(string? value)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
if (value.Contains(Separator) || value.Contains('"') || value.Contains('\n') || value.Contains('\r'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
public static string Cell(object? value) => value switch
{
null => string.Empty,
string s => Escape(s),
bool b => b ? "1" : "0",
IFormattable f => Escape(f.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty),
_ => Escape(Convert.ToString(value, CultureInfo.InvariantCulture))
};
public static string Row(params object?[] cells) =>
string.Join(Separator, cells.Select(Cell));
/// <summary>
/// نام نمایشی مشتری؛ اگر خالی باشد موبایل یا شناسه کاربر.
/// </summary>
public static string CustomerName(string? fullName, string? mobile = null, long? userId = null)
{
if (!string.IsNullOrWhiteSpace(fullName))
return fullName.Trim();
if (!string.IsNullOrWhiteSpace(mobile))
return mobile.Trim();
if (userId is > 0)
return $"کاربر {userId}";
return "—";
}
public static string ToBase64(StringBuilder bodyWithoutSepHint)
{
// Excel در لوکال فارسی با این خط، جداکننده کاما را درست تشخیص می‌دهد
var content = "sep=," + Environment.NewLine + bodyWithoutSepHint;
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(content)).ToArray();
return Convert.ToBase64String(bytes);
}
}
@@ -1,5 +1,6 @@
using System.Text; using System.Text;
using BackOffice.Common.BaseComponents; using BackOffice.Common.BaseComponents;
using BackOffice.Common.Utilities;
using BackOffice.Services.DiscountOrder; using BackOffice.Services.DiscountOrder;
using BackOffice.Pages.DiscountShop.Components; using BackOffice.Pages.DiscountShop.Components;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -245,30 +246,29 @@ public partial class DiscountOrdersMainPage
try try
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("شناسه,نام کاربر,موبایل,مبلغ کل,کیف پول اعتباری,پرداخت درگاه,تعداد آیتم,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت,تاریخ ثبت,آدرس"); sb.AppendLine(CsvExportHelper.Row(
"شناسه", "نام مشتری", "موبایل", "مبلغ کل", "کیف پول اعتباری", "پرداخت درگاه",
"تعداد آیتم", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت", "تاریخ ثبت", "آدرس"));
foreach (var o in _orders) foreach (var o in _orders)
{ {
sb.AppendLine(string.Join(",", sb.AppendLine(CsvExportHelper.Row(
o.Id, o.Id,
EscapeCsv(o.UserFullName), CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
EscapeCsv(o.UserMobile), o.UserMobile,
o.TotalPrice.ToString("N0"), o.TotalPrice,
o.DiscountBalanceUsed.ToString("N0"), o.DiscountBalanceUsed,
o.GatewayAmount.ToString("N0"), o.GatewayAmount,
o.ItemsCount, o.ItemsCount,
EscapeCsv(GetPaymentStatusText(o.PaymentStatusValue)), GetPaymentStatusText(o.PaymentStatusValue),
EscapeCsv(GetStatusText(o.Status)), GetStatusText(o.Status),
o.PaymentDate?.MiladiToJalaliWithTime() ?? "-", o.PaymentDate?.MiladiToJalaliWithTime() ?? "-",
o.Created?.MiladiToJalaliWithTime() ?? "-", o.Created?.MiladiToJalaliWithTime() ?? "-",
EscapeCsv(o.ShippingAddress ?? "-"))); o.ShippingAddress ?? "-"));
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
var base64 = Convert.ToBase64String(bytes);
var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv"; var filename = $"discount-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
@@ -276,12 +276,4 @@ public partial class DiscountOrdersMainPage
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
} }
} }
private static string EscapeCsv(string value)
{
if (string.IsNullOrEmpty(value)) return "";
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
} }
@@ -531,14 +531,15 @@
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("شناسه,نام کاربر,موبایل,تاریخ ثبت,تعداد آیتم,مبلغ نهایی,تخفیف,وضعیت"); sb.AppendLine(CsvExportHelper.Row(
"شناسه", "نام مشتری", "موبایل", "تاریخ ثبت", "تعداد آیتم", "مبلغ نهایی", "تخفیف", "وضعیت"));
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created)) foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
{ {
sb.AppendLine(string.Join(",", sb.AppendLine(CsvExportHelper.Row(
o.Id, o.Id,
EscapeCsv(o.UserFullName), CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId),
EscapeCsv(o.UserMobile), o.UserMobile,
o.Created?.MiladiToJalaliWithTime() ?? "-", o.Created?.MiladiToJalaliWithTime() ?? "-",
o.ItemsCount, o.ItemsCount,
o.GatewayAmount, o.GatewayAmount,
@@ -546,11 +547,8 @@
GetStatusText(o.Status))); GetStatusText(o.Status)));
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
var base64 = Convert.ToBase64String(bytes);
var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv"; var filename = $"discount-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
} }
@@ -575,12 +573,12 @@
sb.AppendLine(); sb.AppendLine();
sb.AppendLine("جزئیات سفارش‌ها:"); sb.AppendLine("جزئیات سفارش‌ها:");
sb.AppendLine("شناسه | نام کاربر | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت"); sb.AppendLine("شناسه | نام مشتری | تاریخ | تعداد آیتم | مبلغ نهایی | تخفیف | وضعیت");
foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created)) foreach (var o in _orders.Where(o => o.Created.HasValue).OrderBy(o => o.Created))
{ {
sb.AppendLine( sb.AppendLine(
$"{o.Id} | {o.UserFullName} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}"); $"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, o.UserMobile, o.UserId)} | {o.Created?.MiladiToJalaliWithTime() ?? "-"} | {o.ItemsCount} | {o.GatewayAmount:N0} | {o.DiscountBalanceUsed:N0} | {GetStatusText(o.Status)}");
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray(); var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
@@ -590,14 +588,4 @@
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64); await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success); Snackbar.Add("خروجی PDF (نسخه متنی) آماده شد.", Severity.Success);
} }
private static string EscapeCsv(string value)
{
if (string.IsNullOrEmpty(value))
return "";
var needsQuotes = value.Contains(',') || value.Contains('"') || value.Contains('\n');
var escaped = value.Replace("\"", "\"\"");
return needsQuotes ? $"\"{escaped}\"" : escaped;
}
} }
@@ -1,4 +1,5 @@
using System.Text; using System.Text;
using BackOffice.Common.Utilities;
using CMSMicroservice.Protobuf.Protos; using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserOrder;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -244,25 +245,23 @@ public partial class OrderSalesReports
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,تاریخ پرداخت"); sb.AppendLine(CsvExportHelper.Row(
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "تاریخ پرداخت"));
foreach (var o in _orders) foreach (var o in _orders)
{ {
sb.AppendLine(string.Join(",", sb.AppendLine(CsvExportHelper.Row(
o.Id, o.Id,
o.UserId, o.UserId,
EscapeCsv(o.UserFullName), CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount, o.Amount,
EscapeCsv(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده"), o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده",
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())), GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")); o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-"));
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
var base64 = Convert.ToBase64String(bytes);
var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.csv"; var filename = $"store-sales-{DateTime.Now:yyyyMMddHHmmss}.csv";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
} }
@@ -292,7 +291,7 @@ public partial class OrderSalesReports
foreach (var o in _orders) foreach (var o in _orders)
{ {
sb.AppendLine( sb.AppendLine(
$"{o.Id} | {o.UserFullName} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}"); $"{o.Id} | {CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId)} | {o.Amount:N0} | {(o.PaymentStatus == PaymentStatus.Success ? "پرداخت شده" : "پرداخت نشده")} | {GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())} | {(o.PaymentDate != null ? o.PaymentDate.ToDateTime().MiladiToJalaliWithTime() : "-")}");
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray(); var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
@@ -351,12 +350,4 @@ public partial class OrderSalesReports
} }
}; };
} }
private static string EscapeCsv(string value)
{
if (string.IsNullOrEmpty(value)) return "";
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
} }
@@ -1,8 +1,9 @@
using System.Text.Json; using System.Text;
using System.Text; using System.Text.Json;
using BackOffice.Common.BaseComponents;
using BackOffice.Common.Utilities;
using CMSMicroservice.Protobuf.Protos; using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserOrder;
using BackOffice.Common.BaseComponents;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using MudBlazor; using MudBlazor;
@@ -242,6 +243,15 @@ public partial class UserOrderMainPage
}; };
} }
// Domain PaymentStatus: Success=0, Reject=1, Pending=2
private static string GetPaymentStatusText(int status) => status switch
{
0 => "پرداخت شده",
1 => "پرداخت ناموفق",
2 => "در انتظار پرداخت",
_ => "نامشخص"
};
private async Task OpenChangeStatus(DataModel model) private async Task OpenChangeStatus(DataModel model)
{ {
var parameters = new DialogParameters var parameters = new DialogParameters
@@ -320,26 +330,24 @@ public partial class UserOrderMainPage
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine("شناسه,کاربر,نام کاربر,مبلغ,وضعیت پرداخت,وضعیت ارسال,روش پرداخت,تاریخ پرداخت"); sb.AppendLine(CsvExportHelper.Row(
"شناسه", "کاربر", "نام مشتری", "مبلغ", "وضعیت پرداخت", "وضعیت ارسال", "روش پرداخت", "تاریخ پرداخت"));
foreach (var o in result.Models) foreach (var o in result.Models)
{ {
sb.AppendLine(string.Join(",", sb.AppendLine(CsvExportHelper.Row(
o.Id, o.Id,
o.UserId, o.UserId,
EscapeCsv(o.UserFullName), CsvExportHelper.CustomerName(o.UserFullName, userId: o.UserId),
o.Amount, o.Amount,
EscapeCsv(o.PaymentStatus.ToString()), GetPaymentStatusText(o.PaymentStatus.GetHashCode()),
EscapeCsv(GetDeliveryStatusText(o.DeliveryStatus.GetHashCode())), GetDeliveryStatusText(o.DeliveryStatus.GetHashCode()),
EscapeCsv(GetPaymentMethodText(o.PaymentMethod.GetHashCode())), GetPaymentMethodText(o.PaymentMethod.GetHashCode()),
o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-")); o.PaymentDate?.ToDateTime().ToLocalTime().MiladiToJalaliWithTime() ?? "-"));
} }
var bytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(sb.ToString())).ToArray();
var base64 = Convert.ToBase64String(bytes);
var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.csv"; var filename = $"user-orders-{DateTime.Now:yyyyMMddHHmmss}.csv";
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, CsvExportHelper.ToBase64(sb));
await JsRuntime.InvokeVoidAsync("jsSaveAsFile", filename, base64);
Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success); Snackbar.Add("خروجی Excel (CSV) آماده شد.", Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
@@ -347,12 +355,4 @@ public partial class UserOrderMainPage
Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error); Snackbar.Add($"خطا در خروجی Excel: {ex.Message}", Severity.Error);
} }
} }
private static string EscapeCsv(string value)
{
if (string.IsNullOrEmpty(value)) return "";
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
} }