Maui Blazor 中文社区 QQ群:645660665

Blazor 拖放上传文件转换格式并推送到浏览器下载

前言

昨天有个小伙伴发了一个老外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;
            if (fileName == null) fileName = ""
            anchorElement.download = fileName;
            anchorElement.click();
            anchorElement.remove();
            URL.revokeObjectURL(url);
        }
    </script>

插播:

突然想起来 下载功能 里面的js代码有坑, 旧的设备例如安卓手机会不能用. 以前文章有写过, 大家注意一下

anchorElement.download = fileName ?? ''; 改为以下代码

    if (fileName == null) fileName = ""
    anchorElement.download = fileName;

组件代码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应该可以用了.

posted @ 2023-02-16 08:45  AlexChow  阅读(1846)  评论(5编辑  收藏  举报