Blazor 拖放上傳檔案轉換格式並推送到瀏覽器下載

AlexChow發表於2023-02-16

前言

昨天有個小夥伴發了一個老外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.*" />

  1. 動態載入 drag.js 檔案.(參考往期文章,js隔離 https://www.cnblogs.com/densen2014/p/16027851.html)
  2. 使用拖放讀取到 IBrowserFile 檔案流
  3. 轉換為 MemoryStream 供給 MiniExcel 讀取. (PS:不能直接使用 IBrowserFile 的 stream , 當作課後作業自己瞭解一下.)
  4. MiniExcel 讀取格式: var mrwTicket = MiniExcel.Query(fs, excelType: ExcelType.CSV).ToList();
  5. 轉換格式
  6. 另存為目標格式csv
  7. 直接彈出目標檔案下載到瀏覽器
  8. 全程不落地
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();
            }
        }

    }

執行

可接受多檔案拖放同時轉換

完整程式碼來的,直接cv應該可以用了.

相關文章