前言
昨天有個小夥伴發了一個老外java編寫的小工具給我,功能是轉換西班牙郵局快遞Coreeos express的單據格式成Amazon格式,他的需求是改一下程式為匹配轉換另一個快遞公司MRW格式到Amazon格式,然而我堂堂一個Blazor發燒友,怎麼可能去反編譯人家的java修改呢?必須直接擼一個Blazor的啊.
實現目標
拖放多檔案上傳, 不落地(無中間儲存臨時檔案)直接記憶體流轉換格式, 直接推送彈出下載檔案.
分析需求
原始MRW檔案.txt
"Abonado","Depto.","Fecha","N. Envio","N. Lote","Tipo de Cobro","Bultos","Kg.","Imp Reemb.","Referencia","Destinatario","Direccion","C.P.","Poblacion","Pais","Servicio","Retorno Alb.",""
"xxx SL ","N/A","15/02/2023","0263608650029","02636xxx20230214204409","Pagados","1","1","","403-6273741-3115504","Antonia xxx FERNANDEZ","C/MENDEZ NUÑEZ 222","06420","CASTUERA","ESPAÑA","U19E--Urgente 19 Expedición","SinRetorno",""
"xxx SL ","N/A","15/02/2023","0263608650028","02636xxx20230214204409","Pagados","1","1","","406-8908494-9500324","Baris xxx","Parque Erreniega Parkea,","31180","CIZUR MAYOR","ESPAÑA","U19E--Urgente 19 Expedición","SinRetorno",""
實體類
來源
public class MrwTicket
{
public string Abonado { get; set; }
[DisplayName("Depto.")]
public string Depto { get; set; }
public DateTime Fecha { get; set; }
[DisplayName("N. Envio")]
public string N_Envio { get; set; }
[DisplayName("N. Lote")]
public string N_Lote { get; set; }
[DisplayName("Tipo de Cobro")]
public string TipoDeCobro { get; set; }
public string Bultos { get; set; }
[DisplayName("Kg.")]
public string Kg { get; set; }
[DisplayName("Imp Reemb.")]
public string ImpReemb { get; set; }
public string Referencia { get; set; }
public string Destinatario { get; set; }
public string Direccion { get; set; }
[DisplayName("C.P.")]
public string CP { get; set; }
public string Poblacion { get; set; }
public string Pais { get; set; }
public string Servicio { get; set; }
[DisplayName("Retorno Alb.")]
public string RetornoAlb { get; set; }
}
轉換目標
public class AmazonTicket
{
[DisplayName("order-id")]
public string Order_id { get; set; }
[DisplayName("order-item-id")]
public string Order_item_id { get; set; }
[DisplayName("quantity")]
public string Quantity { get; set; }
[DisplayName("ship-date")]
public string Ship_date { get; set; }
[DisplayName("carrier-code")]
public string Carrier_code { get; set; }
[DisplayName("carrier-name")]
public string Carrier_name { get; set; }
[DisplayName("tracking-number")]
public string Tracking_number { get; set; }
[DisplayName("ship-method")]
public string Ship_method { get; set; }
}
建立Blazor頁面 Mrw2Amazon.razor
拖放上傳可以參考往期文章 https://www.cnblogs.com/densen2014/p/16128246.html
元件UI
@page "/Mrw2Amazon"
@inherits PublicComponentsBase
@namespace AmeBlazor.Components
<h4>MRW txt 轉 Amazon txt</h4>
<PageTitle>MRW txt 轉 Amazon txt</PageTitle>
<div @ref="UploadElement" style="padding: 20px; width: 200px; height: 200px; background-color: cornflowerblue; border: 2px dashed #0087F7; border-radius: 5px; ">
<p>拖放上傳檔案</p>
<InputFile OnChange="OnChange" class="form-control" multiple @ref="inputFile" />
</div>
<pre>
<code>
@uploadstatus
</code>
</pre>
拖放上傳js檔案 wwwroot/drag.js
export function init(wrapper, element, inputFile) {
//阻止瀏覽器預設行為
document.addEventListener("dragleave", function (e) {
e.preventDefault();
}, false);
document.addEventListener("drop", function (e) {
e.preventDefault();
}, false);
document.addEventListener("dragenter", function (e) {
e.preventDefault();
}, false);
document.addEventListener("dragover", function (e) {
e.preventDefault();
}, false);
element.addEventListener("drop", function (e) {
try {
var fileList = e.dataTransfer.files; //獲取檔案物件
//檢測是否是拖拽檔案到頁面的操作
if (fileList.length == 0) {
return false;
}
inputFile.files = e.dataTransfer.files;
const event = new Event('change', { bubbles: true });
inputFile.dispatchEvent(event);
}
catch (e) {
wrapper.invokeMethodAsync('DropAlert', e);
}
}, false);
element.addEventListener('paste', function (e) {
inputFile.files = e.clipboardData.files;
const event = new Event('change', { bubbles: true });
inputFile.dispatchEvent(event);
}, false);
return {
dispose: () => {
element.removeEventListener('dragleave', onDragLeave);
element.removeEventListener("drop", onDrop);
element.removeEventListener('dragenter', onDragHover);
element.removeEventListener('dragover', onDragHover);
element.removeEventListener('paste', handler);
}
}
}
下載功能
Pages\_Layout.cshtml
< /body >之前新增js程式碼
<script>
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
</script>
元件程式碼Mrw2Amazon.razor.cs
先拉個庫MiniExcel
<PackageReference Include="MiniExcel" Version="1.*" />
- 動態載入 drag.js 檔案.(參考往期文章,js隔離 https://www.cnblogs.com/densen2014/p/16027851.html)
- 使用拖放讀取到 IBrowserFile 檔案流
- 轉換為 MemoryStream 供給 MiniExcel 讀取. (PS:不能直接使用 IBrowserFile 的 stream , 當作課後作業自己瞭解一下.)
- MiniExcel 讀取格式: var mrwTicket = MiniExcel.Query
(fs, excelType: ExcelType.CSV).ToList(); - 轉換格式
- 另存為目標格式csv
- 直接彈出目標檔案下載到瀏覽器
- 全程不落地
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using MiniExcelLibs;
using MiniExcelLibs.Csv;
public partial class Mrw2Amazon : IAsyncDisposable
{
[Inject]
IJSRuntime JS { get; set; }
//既然不落地,那就不需要這些了
//[Inject]
//protected Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment { get; set; }
protected ElementReference UploadElement { get; set; }
protected InputFile? inputFile { get; set; }
private DotNetObjectReference<Mrw2Amazon>? wrapper;
private IJSObjectReference? module;
private IJSObjectReference? dropInstance;
//既然不落地,那就不需要這些了
//protected string UploadPath = "";
protected string? uploadstatus;
long maxFileSize = 1024 * 1024 * 15;
//既然不落地,那就不需要這些了
//protected override void OnAfterRender(bool firstRender)
//{
//if (!firstRender) return;
//UploadPath = Path.Combine(HostEnvironment!.WebRootPath, "uploads", "temp"); //初始化上傳路徑
//if (!Directory.Exists(UploadPath)) Directory.CreateDirectory(UploadPath); //不存在則新建目錄
//}
protected async Task OnChange(InputFileChangeEventArgs e)
{
int i = 0;
var selectedFiles = e.GetMultipleFiles(100);
foreach (var item in selectedFiles)
{
i++;
await OnSubmit(item);
uploadstatus += Environment.NewLine + $"[{i}]: " + item.Name;
}
}
protected async Task OnSubmit(IBrowserFile efile)
{
try
{
if (efile == null) return;
if (efile.ContentType != "text/plain")
{
uploadstatus += Environment.NewLine + $"只接受txt檔案.{efile.Name}為{efile.ContentType}";
return;
}
await using var fs = new MemoryStream();
using var stream = efile.OpenReadStream(maxFileSize);
await stream.CopyToAsync(fs);
var mrwTicket = MiniExcel.Query<MrwTicket>(fs, excelType: ExcelType.CSV).ToList();
var amazonTicket = new List<AmazonTicket>();
foreach (var item2 in mrwTicket)
{
amazonTicket.Add(new AmazonTicket()
{
Order_id = item2.Referencia,
Ship_date = item2.Fecha.ToString("MM-dd-yyyy"),
Carrier_code = "MRW",
Tracking_number = item2.N_Envio.Remove(5, 1),
Ship_method = "Urgente 19",
});
}
var memoryStream = new MemoryStream();
memoryStream.SaveAs(amazonTicket, excelType: ExcelType.CSV, configuration: new CsvConfiguration() { Seperator = '\t' });
memoryStream.Seek(0, SeekOrigin.Begin);
using var streamRef = new DotNetStreamReference(stream: memoryStream);
await JS.InvokeVoidAsync("downloadFileFromStream", Path.GetFileNameWithoutExtension(efile.Name) + "_amazon.txt", streamRef);
uploadstatus += Environment.NewLine + $"{efile.Name} 轉換OK";
}
catch (Exception e)
{
uploadstatus += Environment.NewLine + $"轉換出錯 {e.Message}";
}
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
module = await JS.InvokeAsync<IJSObjectReference>("import", "./drag.js");
wrapper = DotNetObjectReference.Create(this);
dropInstance = await module.InvokeAsync<IJSObjectReference>("init", wrapper, UploadElement, inputFile!.Element);
}
[JSInvokable]
public void DropAlert(string msg)
{
uploadstatus += Environment.NewLine + $"[!Alert!]: " + msg;
StateHasChanged();
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (dropInstance != null)
{
await dropInstance.InvokeVoidAsync("dispose");
await dropInstance.DisposeAsync();
}
if (wrapper != null)
{
wrapper.Dispose();
}
if (module != null)
{
await module.DisposeAsync();
}
}
}
執行
可接受多檔案拖放同時轉換