Ext.js5表单的文件上传(toast)(30)

本文介绍了一个使用Ext.form.field.File实现的文件上传组件示例,包括基本文件上传字段、仅按钮形式的文件选择、多文件上传表单及错误处理等特性。

view

/**
 * This example demonstrates use of Ext.form.field.File, a file upload field with custom
 * rendering, and error handling.
 * 文件上传,自定义字段呈现和错误处理
 */
Ext.define('KitchenSink.view.form.FileUploads', {
    extend: 'Ext.container.Container',
    xtype: 'form-fileuploads',
    controller: 'form-fileuploads',


    width: 600,
    layout: {
        type: 'vbox',
        align: 'stretch'
    },

    defaults: {
        xtype: 'form',
        layout: 'anchor',

        bodyPadding: 10,
        style: {
            'margin-bottom': '20px'
        },

        defaults: {
            anchor: '100%'
        }
    },

    items: [{
        items: [{
            xtype: 'component',
            html: [
                '<h3>Basic File Field</h3>',
                //这一个是典型的文件上传方式,文本框是只读的,文件的路径getValue方法取决于浏览器。
                '<p>A typical file upload field with Ext style. Direct editing ',
                'of the text field cannot be done in a consistent, cross-browser way, ',
                'so it is always read-only. The file path reported by the ',
                '<code>getValue</code> method will depend on the browser and cannot ',
                'be controlled by Ext JS.'
            ]
        }, {
            xtype: 'filefield',
            hideLabel: true,
            reference: 'basicFile'
        }, {
            xtype: 'button',
            text: 'Get File Path',
            handler: 'getFilePath'
        }]
    }, {
        items: [{
            xtype: 'component',
            html: [
                '<h3>Button Only</h3>',
                //通过按钮上传文件,值得注意的是提示是在右下角出现的,很好。试试用来取代alert.pop.confirm。
                //
                '<p>You can also render the file input as a button without ',
                'the text field, with access to the field\'s value via the ',
                'standard <tt>Ext.form.field.Field</tt> interface or by handling ',
                'the <tt>change</tt> event (as in this example).',
                '</p>'
            ]
        }, {
            xtype: 'fileuploadfield', // Same as filefield above
            buttonOnly: true,
            hideLabel: true,
            listeners: {
                change: 'buttonOnlyChange'
            }
        }]
    }, {
    //这个可以一次传很多文件
        title: 'File Upload Form',
        frame: true,
        bodyPadding: '10 10 0',
        reference: 'firstForm',

        defaults: {
            anchor: '100%',
            allowBlank: false,
            msgTarget: 'side',
            labelWidth: 50
        },

        items: [{
            xtype: 'textfield',
            fieldLabel: 'Name'
        }, {
            xtype: 'filefield',
            emptyText: 'Select an image',
            fieldLabel: 'Photo',
            name: 'photo-path',
            buttonText: '',
            buttonConfig: {
                iconCls: 'upload-icon'
            }
        }],

        buttons: [{
            text: 'Save',
            handler: 'firstFormSave'
        }, {
            text: 'Reset',
            handler: 'firstFormReset'
        }]
    }, {
        title: 'Upload error test',
        frame: true,
        bodyPadding: '10 10 0',
        reference: 'secondForm',

        defaults: {
            anchor: '100%',
            allowBlank: false,
            msgTarget: 'side',
            labelWidth: 70
        },

        items: [{
            xtype: 'textfield',
            fieldLabel: 'Name'
        }, {
            xtype: 'filefield',
            emptyText: 'Select an image',
            fieldLabel: 'Photo',
            name: 'photo-path',
            buttonConfig: {
                text : '',
                iconCls: 'upload-icon'
            }
        }, {
            xtype: 'numberfield',
            fieldLabel: 'HTTP status',
            value: 200,
            minValue: 200,
            maxValue: 599,
            name: 'returnResponse'
        }],

        buttons: [{
            text: 'Save',
            handler: 'secondFormSubmit'
        }, {
            text: 'Reset',
            handler: 'secondFormReset'
        }]
    }]
});

viewcontroller

Ext.define('KitchenSink.view.form.FileUploadsController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.form-fileuploads',

    getFilePath: function() {
        var v = this.lookupReference('basicFile').getValue();
        //正常情况下的alert
        Ext.Msg.alert('Selected File', v && v !== '' ? v : 'None');
    },

    buttonOnlyChange: function(field, value) {
    //嗷嗷,终于知道那种是什么呢!哈哈哈,漂亮的样式,新添的吐司哈哈哈!
        Ext.toast('<b>Selected:</b> ' + value);
    },

    firstFormSave: function() {
        var form = this.lookupReference('firstForm').getForm();

        if (form.isValid()) {
            form.submit({
                url: 'resources/data/form/file-upload.php',
                waitMsg: 'Uploading your photo...',
                success: function(fp, o) {
                    var tpl = new Ext.XTemplate(
                        'File processed on the server.<br />',
                        'Name: {fileName}<br />',
                        'Size: {fileSize:fileSize}'
                    );

                    Ext.Msg.alert('Success', tpl.apply(o.result));
                }
            });
        }
    },

    firstFormReset: function() {
        this.lookupReference('firstForm').getForm().reset();
    },

    secondFormSubmit: function() {
        var form = this.lookupReference('secondForm').getForm();

        if (form.isValid()) {
            form.submit({
                url: 'resources/data/form/file-upload.php',
                waitMsg: 'Uploading your photo...',
                success: this.secondFormUploadSuccess,
                failure: this.secondFormUploadFailure
            });
        }
    },

    secondFormReset: function() {
        this.lookupReference('secondForm').getForm().reset();
    },

    secondFormUploadSuccess: function(form, action) {
        Ext.Msg.alert('Success', 'Processed file "' + action.result.file + '" on the server');
    },

    secondFormUploadFailure: function(form, action) {
        Ext.Msg.alert("Error", Ext.JSON.decode(this.response.responseText).message);
    }
});
<think>嗯,用户现在想要做一个使用jQuery、Bootstrap5和ASP.NET MVC的页面,点击按钮下载文件,并且要在文件下载完成后提示下载成功。首先,我需要理清楚整个流程,前后端如何配合。 首先,前端部分需要有一个按钮,用户点击后触发下载。使用Bootstrap5的话,按钮样式可以很容易地美化。然后,使用jQuery来处理点击事件,发送请求到后端。这时候需要考虑如何监测文件下载完成,因为浏览器下载文件是异步的,普通的AJAX请求可能无法直接获取下载完成的事件。 用户提到要在下载完成后提示成功,但常规的AJAX下载文件可能不太适用,因为文件下载是通过浏览器处理的,而不是通过AJAX的response。所以可能需要其他方法。一种常见的方法是通过表单提交或者创建一个隐藏的iframe,这样不会阻塞页面,同时可以监听iframe的load事件来判断是否下载完成。 不过,ASP.NET MVC中,通常文件下载是通过返回FileResult来实现的。前端点击按钮,发送请求到某个Action,该Action返回文件,浏览器处理下载。这时候,如何在前端知道下载完成呢?可能需要后端在文件传输完成后发送某种信号,但HTTP协议是单次请求响应的,所以可能需要前端轮询或者使用WebSocket,不过这样比较复杂。 另一种方法是,前端在发送下载请求后,设置一个定时器检查某个cookie是否存在,后端在文件传输完成后设置这个cookie。但这种方法也有局限性,比如跨域问题或者定时器的不准确。 或者,可以结合使用前端隐藏的iframe,当用户点击按钮时,生成一个iframe,src指向下载的URL,然后监听iframe的load事件,假设load事件触发时下载完成。但实际情况中,浏览器的下载行为可能不会触发iframe的load事件,因为下载的文件不会在iframe中显示。所以这种方法可能不可靠。 可能需要采用另一种方式:前端发送请求到后端,后端开始准备文件,并立即返回一个唯一的下载ID,然后前端轮询后端,询问该ID对应的文件是否已准备好下载。当准备好后,前端再触发下载,并监听下载完成。但这种方法需要后端支持生成下载ID和状态跟踪,可能增加后端复杂度。 或者,可以利用浏览器的下载行为,前端在触发下载后,通过检测浏览器下载的文件是否完成。但出于安全原因,JavaScript无法直接访问用户的文件系统,所以这种方法不可行。 看来这个问题比较棘手。或许用户的需求其实可以通过在文件下载请求发送后,假设后端能够快速传输文件,然后前端在发送请求后显示一个加载提示,并在一定时间后假设下载完成,显示成功消息。但这种方法不够准确,特别是大文件下载时,时间可能很长,导致提示过早出现。 或者,可以考虑使用AJAX下载文件,将文件内容获取为二进制数据,然后使用JavaScript生成Blob并触发下载。这种方法可以捕获AJAX的complete事件,从而知道下载何时完成。但这种方法适用于小文件,因为大文件可能会导致内存问题,并且需要处理二进制数据的正确转换。 现在需要结合ASP.NET MVC后端。如果使用AJAX获取文件,后端需要返回文件的字节数组,前端将其转换为Blob并创建下载链接。这种方法的话,确实可以在AJAX的success回调中提示用户下载完成。但需要注意设置正确的MIME类型和文件名。 那具体步骤可能是这样的: 前端使用jQuery的AJAX方法,设置responseType为'blob',发送GET请求到后端的一个Action,该Action返回FileContentResult。在AJAX的success回调中,创建一个Blob对象,生成临时URL,模拟点击下载,然后清除临时URL。此时,success回调会在文件下载完成后触发吗?其实,AJAX请求完成时,文件数据已经下载到浏览器中,但用户需要手动点击生成的链接,或者使用代码自动触发下载,这时候的“下载完成”指的是Blob数据已经接收完毕,而不是用户实际完成保存文件到本地。因此,提示可能会在用户开始下载前就显示,这不符合用户的需求。 所以,这种方法可能无法准确检测用户何时完成文件的保存。因此,可能需要另一种方式。 回到最初的思路,使用隐藏的iframe,当用户点击按钮时,设置iframe的src为下载URL,然后监听iframe的load事件。虽然下载文件不会在iframe中显示内容,但如果后端正确返回文件,浏览器会处理下载,而iframe的load事件会在下载开始时触发,而不是完成时。所以这种方法不可靠。 或许用户的需求其实可以简化为:点击按钮后,显示“下载中”的提示,当浏览器的下载行为被触发后,就显示“下载完成”,但实际上这并不能准确知道用户何时完成下载。但可能用户只是希望在文件开始下载后提示成功,而不是确切知道下载完成。这时候,可以在按钮点击后,显示提示,比如使用Bootstrap的模态框或Toast组件,提示“文件正在下载”,然后在一定时间后自动关闭或提示成功。但这并不是真正的检测下载完成。 这时候可能需要与用户沟通需求,明确是否必须准确检测下载完成,或者是否可以近似处理。但根据用户的问题描述,他希望的是“等文件下载完成之后再提示下载成功”,所以需要准确检测。 可能这时候需要采用服务器端的一些配合,比如生成一个唯一的下载令牌,当用户开始下载时,服务器记录该令牌为“进行中”,当文件传输完成后,标记为“完成”。前端在触发下载后,定期向服务器查询该令牌的状态,直到状态为“完成”时提示成功。但这种方法需要后端记录状态,可能涉及会话或数据库存储,增加了复杂性。 或者,考虑使用服务器推送技术,如SignalR,在文件下载完成后,后端通知前端。但这也需要建立WebSocket连接,对于简单的文件下载来说可能过于复杂。 或许在这种情况下,更实际的解决方案是,当用户点击下载按钮后,显示一个提示,比如“下载已开始,请查看浏览器的下载列表”,而不尝试检测何时完成。或者,如果文件较小,可以在一段时间后自动提示成功,但这不够准确。 但用户明确要求要提示下载完成,所以必须找到可行的方法。这时候可能需要使用AJAX下载文件,并在成功回调中生成Blob并触发下载,此时可以显示成功提示,但用户需要额外点击浏览器的保存对话框,这可能在异步处理中导致问题。 或者,考虑使用Fetch API配合Blob和Object URL,例如: fetch(url) .then(response => response.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'filename.ext'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); // 显示成功提示 }); 这种方法中,fetch请求完成后,创建并触发下载链接,此时success回调中可以显示提示。但问题在于,浏览器可能会在后台下载文件,而用户可能还没有完成保存操作(比如在Chrome中自动下载到默认路径,不需要用户交互),此时提示是即时的。如果用户需要手动选择保存位置,则提示可能在选择保存对话框出现之前就显示,这不符合需求。 不过,根据实际情况,大多数现代浏览器处理下载时,如果是静默下载(没有弹出对话框),则提示可以立即显示;如果需要用户交互,则无法准确知道何时完成。因此,这种方法可能无法满足所有情况,但可能是最接近的解决方案。 结合用户的技术栈,ASP.NET MVC后端需要提供一个Action,返回FileResult,例如: public ActionResult DownloadFile() { byte[] fileBytes = // 获取文件字节数组 string fileName = "example.txt"; return File(fileBytes, "application/octet-stream", fileName); } 前端使用jQuery发送AJAX请求,但由于需要获取Blob数据,需要设置正确的dataType和processData选项。或者使用Fetch API更简单。 综上,可能的解决方案是: 1. 前端使用Fetch API请求文件,接收为Blob。 2. 创建临时URL,模拟点击下载链接。 3. 在Fetch的then回调中显示成功提示。 但这样提示会在文件数据下载完成后触发,而不是用户完成保存到本地。但用户可能认为数据已经传输完毕,所以可以接受。 现在,结合用户要求的jQuery和Bootstrap5,代码可能如下: HTML部分有一个按钮,点击后触发下载,使用Bootstrap样式: <button id="downloadBtn" class="btn btn-primary">下载文件</button> <div id="message" class="alert alert-3" style="display:none;">下载成功!</div> jQuery部分: $('#downloadBtn').click(function() { var $btn = $(this); $btn.prop('disabled', true); $('#message').hide(); fetch('/Home/DownloadFile') .then(response => response.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'filename.ext'; // 需要从响应头获取文件名 document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); $('#message').show(); $btn.prop('disabled', false); }) .catch(error => { console.error('下载失败:', error); alert('下载失败,请重试!'); $btn.prop('disabled', false); }); }); 但这里的问题是无法从fetch的响应中获取文件名,因为ASP.NET MVC的FileResult会自动添加Content-Disposition头,其中包含文件名。因此,需要从响应头中提取文件名。 修改后的代码: fetch('/Home/DownloadFile') .then(response => { const filename = response.headers.get('Content-Disposition').split('filename=')[1].replace(/"/g, ''); return response.blob().then(blob => ({ blob, filename })); }) .then(({ blob, filename }) => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); $('#message').show(); $btn.prop('disabled', false); }) .catch(error => { // 错误处理 }); 但需要注意,CORS情况下可能需要暴露Content-Disposition头,但在同源的情况下应该没问题。 后端Action需要设置正确的Content-Disposition: public FileResult DownloadFile() { byte[] fileBytes = System.IO.File.ReadAllBytes(@"path\to\file"); string fileName = "example.txt"; return File(fileBytes, "application/octet-stream", fileName); } 这样,Content-Disposition头会被自动设置为attachment; filename="example.txt",前端可以解析出来。 这样,当fetch请求完成,Blob数据下载完毕后,会创建临时链接并触发下载,然后在成功回调中显示提示。这样,提示会在文件数据从服务器传输到浏览器后显示,而浏览器开始下载文件到本地的时间可能稍后,但用户可能感知为下载完成。 但用户的需求是“等文件下载完成之后再提示下载成功”,所以这种方法可能不完全符合,但可能是最接近的解决方案,因为无法通过JavaScript准确检测用户何时保存完成。 因此,在回答中需要说明这一点,即提示会在文件从服务器传输到浏览器后显示,但实际保存到本地的时间可能因浏览器而异,无法精确检测。但通常用户会认为文件可用时即为成功。 最后,编写完整的代码示例,包括前端HTML、jQuery、Bootstrap部分,以及后端的ASP.NET MVC Action,并解释各个部分的作用和限制。以下是</think>结合 **jQuery、Bootstrap 5 和 ASP.NET MVC** 实现“点击按钮下载文件,下载完成后提示成功”的完整代码及解释: --- ## **实现思路** 1. **前端**:通过 `fetch` 请求文件数据,利用 `Blob` 和临时 URL 触发浏览器下载。 2. **后端**:ASP.NET MVC 提供文件下载的 Action,返回 `FileResult`。 3. **完成检测**:当文件数据从服务器传输到浏览器后,触发成功提示(实际保存到本地的时间由浏览器控制,无法精确检测)。 --- ## **前端代码(HTML + jQuery + Bootstrap)** ```html @* Views/Home/Index.cshtml *@ <!-- 按钮和提示框 --> <button id="downloadBtn" class="btn btn-primary">下载文件</button> <div id="successAlert" class="alert alert-success mt-3" style="display: none;"> 文件下载成功! </div> <!-- jQuery 脚本 --> @section Scripts { <script> $(document).ready(function() { $('#downloadBtn').click(function() { const $btn = $(this); $btn.prop('disabled', true); // 禁用按钮防止重复点击 // 发起下载请求 fetch('@Url.Action("DownloadFile", "Home")') .then(response => { // 从响应头中提取文件名 const contentDisposition = response.headers.get('Content-Disposition'); const fileName = contentDisposition .split('filename=')[1] .replace(/"/g, '') .trim(); return response.blob().then(blob => ({ blob, fileName })); }) .then(({ blob, fileName }) => { // 创建临时 URL 并触发下载 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); // 释放内存 document.body.removeChild(a); // 显示成功提示 $('#successAlert').fadeIn().delay(3000).fadeOut(); }) .catch(error => { console.error('下载失败:', error); alert('下载失败,请重试!'); }) .finally(() => { $btn.prop('disabled', false); // 恢复按钮 }); }); }); </script> } ``` --- ## **后端代码(ASP.NET MVC Controller)** ```csharp // Controllers/HomeController.cs using System.Web.Mvc; public class HomeController : Controller { public ActionResult Index() { return View(); } public FileResult DownloadFile() { // 示例:生成或读取文件字节数据 byte[] fileBytes = System.IO.File.ReadAllBytes(@"C:\Files\example.txt"); string fileName = "example.txt"; // 浏览器将以此名称保存文件 // 返回文件流 return File(fileBytes, "application/octet-stream", fileName); } } ``` --- ## **关键点解释** ### 1. **前端流程** - **按钮交互**:点击后禁用按钮,防止重复请求。 - **Fetch API**:代替传统 AJAX,直接获取二进制数据(`response.blob()`)。 - **文件名提取**:从响应头 `Content-Disposition` 解析服务器返回的文件名。 - **Blob 下载**:通过创建 `<a>` 标签模拟点击,利用浏览器原生下载行为。 - **内存释放**:`revokeObjectURL` 防止内存泄漏。 ### 2. **后端流程** - **FileResult**:ASP.NET MVC 内置的 `File()` 方法直接返回文件流。 - **响应头自动处理**:`File` 方法会自动设置 `Content-Disposition`,指示浏览器下载而非预览。 --- ## **注意事项** - **文件大小限制**:`fetch` 适合中小文件,大文件需分片处理或直接使用 `<a>` 标签跳转。 - **完成检测局限性**:提示在文件数据传输到浏览器后触发,用户实际保存到本地的完成时间无法精确检测。 - **浏览器兼容性**:`fetch` 和 `Blob` 在现代浏览器中支持良好,如需支持旧版 IE,需改用 `XMLHttpRequest`。 --- ## **扩展场景** ### 1. **显示下载进度** ```javascript fetch(url) .then(response => { const reader = response.body.getReader(); const contentLength = +response.headers.get('Content-Length'); let receivedLength = 0; const chunks = []; return reader.read().then(function processChunk({ done, value }) { if (done) { return new Blob(chunks); } chunks.push(value); receivedLength += value.length; console.log(`下载进度: ${(receivedLength / contentLength * 100).toFixed(2)}%`); return reader.read().then(processChunk); }); }); ``` ### 2. **后端动态生成文件** ```csharp public FileResult GenerateCsv() { var sb = new StringBuilder(); sb.AppendLine("姓名,年龄"); sb.AppendLine("张三,30"); sb.AppendLine("李四,25"); byte[] data = Encoding.UTF8.GetBytes(sb.ToString()); return File(data, "text/csv", "users.csv"); } ``` --- 通过以上代码,可以实现一个用户友好的文件下载功能,结合 Bootstrap 的样式和 jQuery 的交互逻辑,同时确保后端高效响应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值