Enterprise-level component library based on Bootstrap and Blazor

star nuget master download license repo commit

Upload 上传

通过点击或者拖拽上传文件

基本用法

InputUpload 组件与其他表单组件一起使用,显示文件名称,点击 浏览 按钮后选择文件并上传;通过设置 ShowRemoveButton 参数,显示 删除 按钮,点击删除按钮时回调 OnDelete 委托方法

Demo

表单应用

在表单内使用文件上传组件对文件格式进行约束

Demo
  • 使用 ValidateForm 表单组件,通过设置模型属性的 FileValidation 标签设置自定义验证,支持文件 扩展名 大小 验证,本例中设置扩展名为 .png .jpg .jpeg,文件大小限制为 50K
  • 选择文件后并未开始上传文件,点击 提交 按钮数据验证合法后,再 OnSubmit 回调委托中进行上传文件操作,注意 Picture 属性类型为 IBrowserFile

点击上传

ButtonUpload 组件,经典款式,用户点击按钮弹出文件选择框。

Demo

设置 IsSingle 时,仅可以上传一张图片或者文件

已上传文件列表

使用 DefaultFileList 设置已上传的内容

Demo
Test.xls (0 B)
Test.doc (0 B)
Test.ppt (0 B)
Test.mp3 (0 B)
Test.mp4 (0 B)
Test.pdf (0 B)
Test.cs (0 B)
Test.zip (0 B)
Test.txt (0 B)
Test.dat (0 B)

上传文件夹

使用 DefaultFileList 设置已上传的内容

Demo

用户头像上传

AvatarUpload 组件,使用 OnChange 限制用户上传的图片格式和大小。本例中仅允许上传 jpg/png/bmp/jpeg/gif 五种图片格式

Demo

卡片形式头像框

圆形头像框

设置 IsSingle 时,仅可以上传一张图片或者文件

组件提供了 Accept 属性用于设置上传文件过滤功能,本例中圆形头像框接受 GIF 和 JPEG 两种图像,设置 Accept="image/gif, image/jpeg",如果不限制图像的格式,可以写为:Accept="image/*",该属性并不安全还是应该是使用 服务器端验证 进行文件格式验证

相关文档:[Accept 属性详解] [Media Types 详细列表]

预览卡片式

CardUpload 组件,呈现为卡片式带预览模式

Demo

Server Side 模式中可以使用 IWebHostEnviroment 注入服务获取到 wwwwroot 目录,保存文件到 images\uploader 中,此功能无需 MVC 的控制器辅助进行文件的保存,直接调用 SaveToFile 方法即可
wasm 模式中无法使用 IWebHostEnviroment 需要调用 webapi 接口等形式将文件保存到服务器端
有兴趣的同学可以通过开源仓库中的 wiki 文档中相关资源查看关于 Upload 组件的相关知识技巧 [传送门]
本例中通过服务器端验证当文件大小超过 200MB 时,提示文件太大提示信息

本例中设置 ShowProgress=true 显示上传进度条

设置 IsSingle 时,仅可以上传一张图片或者文件

文件图标

不同文件格式显示的图标不同

Demo
Test.xls (0 B)
Test.doc (0 B)
Test.ppt (0 B)
Test.mp3 (0 B)
Test.mp4 (0 B)
Test.pdf (0 B)
Test.cs (0 B)
Test.zip (0 B)
Test.txt (0 B)
Test.dat (0 B)

InputUpload

Loading...

ButtonUpload/CardUpload

Loading...

AvatarUpload

Loading...
@page "/uploads"

<h3>Upload 上传</h3>

<h4>通过点击或者拖拽上传文件</h4>

<Block Title="基本用法" Introduction="<code>InputUpload</code> 组件与其他表单组件一起使用,显示文件名称,点击 <b>浏览</b> 按钮后选择文件并上传;通过设置 <code>ShowRemoveButton</code> 参数,显示 <b>删除</b> 按钮,点击删除按钮时回调 <code>OnDelete</code> 委托方法">
    <div class="form-inline">
        <div class="row">
            <div class="form-group col-12 col-sm-6">
                <label for="text1" class="control-label">姓名:</label>
                <input id="text1" class="form-control">
            </div>
            <div class="form-group col-12 col-sm-6">
                <label for="text2" class="control-label">地址:</label>
                <input id="text2" class="form-control">
            </div>
            <div class="form-group col-12">
                <label for="text3" class="control-label">照片:</label>
                <InputUpload Id="text3" ShowDeleteButton="true" OnChange="@OnFileChange" OnDelete="@OnFileDelete"></InputUpload>
            </div>
        </div>
    </div>
    <Logger @ref="Trace" />
</Block>

<Block Title="表单应用" Introduction="在表单内使用文件上传组件对文件格式进行约束">
    <ul class="ul-demo">
        <li>使用 <code>ValidateForm</code> 表单组件,通过设置模型属性的 <code>FileValidation</code> 标签设置自定义验证,支持文件 <b>扩展名</b> <b>大小</b> 验证,本例中设置扩展名为 <code>.png .jpg .jpeg</code>,文件大小限制为 <code>50K</code></li>
        <li>选择文件后并未开始上传文件,点击 <code>提交</code> 按钮数据验证合法后,再 <code>OnSubmit</code> 回调委托中进行上传文件操作,注意 <b>Picture</b> 属性类型为 <code>IBrowserFile</code></li>
    </ul>
    <ValidateForm Model="Foo" OnValidSubmit="OnSubmit" class="form-inline">
        <div class="row">
            <div class="form-group col-12">
                <BootstrapInput @bind-Value="@Foo.Name" />
            </div>
            <div class="form-group col-12">
                <InputUpload @bind-Value="@Foo.Picture" />
            </div>
            <div class="form-group col-12">
                <Button ButtonType="@ButtonType.Submit" Text="提交"></Button>
            </div>
        </div>
    </ValidateForm>
</Block>

<Block Title="点击上传" Introduction="<code>ButtonUpload</code> 组件,经典款式,用户点击按钮弹出文件选择框。">
    <div class="row">
        <div class="col-12 col-sm-6">
            <ButtonUpload ShowProgress="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
        </div>
    </div>
    <p>设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件</p>
    <div class="row mt-3">
        <div class="col-12 col-sm-6">
            <ButtonUpload IsSingle="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
        </div>
    </div>
</Block>

<Block Title="已上传文件列表" Introduction="使用 <code>DefaultFileList</code> 设置已上传的内容">
    <div class="row">
        <div class="col-12 col-sm-6">
            <ButtonUpload OnDelete="@(fileName => Task.FromResult(true))" DefaultFileList="@DefaultFormatFileList"></ButtonUpload>
        </div>
    </div>
</Block>

<Block Title="上传文件夹" Introduction="使用 <code>DefaultFileList</code> 设置已上传的内容">
    <div class="row">
        <div class="col-12 col-sm-6">
            <ButtonUpload IsDirectory="true" OnChange="@OnUploadFolder" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
        </div>
    </div>
</Block>

<Block Title="用户头像上传" Introduction="<code>AvatarUpload</code> 组件,使用 <code>OnChange</code> 限制用户上传的图片格式和大小。本例中仅允许上传 <code>jpg/png/bmp/jpeg/gif</code> 五种图片格式">
    <div class="row">
        <div class="col-12">
            <p>卡片形式头像框</p>
            <AvatarUpload Accept="image/*" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
    </div>
    <div class="row mt-3">
        <div class="col-12">
            <p>圆形头像框</p>
            <AvatarUpload Accept="image/*" ShowProgress="true" IsCircle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
    </div>
    <div class="row mt-3">
        <div class="col-12">
            <p>设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件</p>
            <AvatarUpload IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
            <p>
                <div>组件提供了 <code>Accept</code> 属性用于设置上传文件过滤功能,本例中圆形头像框接受 GIF 和 JPEG 两种图像,设置 <code>Accept="image/gif, image/jpeg"</code>,如果不限制图像的格式,可以写为:<code>Accept="image/*"</code>,该属性并不安全还是应该是使用 <b>服务器端验证</b> 进行文件格式验证</div>
            </p>
            <p>相关文档:<a href="https://www.runoob.com/tags/att-input-accept.html" target="_blank">[Accept 属性详解]</a> <a href="http://www.iana.org/assignments/media-types/media-types.xhtml" target="_blank">[Media Types 详细列表]</a></p>
            <AvatarUpload Accept="image/gif, image/jpeg" IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
    </div>
</Block>

<Block Title="预览卡片式" Introduction="<code>CardUpload</code> 组件,呈现为卡片式带预览模式">
    <p>
        <div><code>Server Side</code> 模式中可以使用 <code>IWebHostEnviroment</code> 注入服务获取到 <code>wwwwroot</code> 目录,保存文件到 <code>images\uploader</code> 中,此功能无需 <b>MVC</b> 的控制器辅助进行文件的保存,直接调用 <code>SaveToFile</code> 方法即可</div>
        <div><code>wasm</code> 模式中无法使用 <code>IWebHostEnviroment</code> 需要调用 <code>webapi</code> 接口等形式将文件保存到服务器端</div>
        <div>有兴趣的同学可以通过开源仓库中的 <code>wiki</code> 文档中相关资源查看关于 <code>Upload</code> 组件的相关知识技巧 <a href="@SiteOptions.Value.VideoLibUrl" target="_blank">[传送门]</a></div>
        <div>本例中通过服务器端验证当文件大小超过 <code>200MB</code> 时,提示文件太大提示信息</div>
    </p>
    <div class="row">
        <div class="col-12">
            <p>本例中设置 <code>ShowProgress=true</code> 显示上传进度条</p>
            <CardUpload ShowProgress="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
        </div>
    </div>
    <div class="row mt-3">
        <div class="col-12">
            <p>设置 <code>IsSingle</code> 时,仅可以上传一张图片或者文件</p>
            <CardUpload IsSingle="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
        </div>
    </div>
</Block>

<Block Title="文件图标" Introduction="不同文件格式显示的图标不同">
    <CardUpload DefaultFileList="@DefaultFormatFileList" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
</Block>

<Toast />

<AttributeTable Items="@GetInputAttributes()" Title="InputUpload" />

<AttributeTable Items="@GetButtonAttributes()" Title="ButtonUpload/CardUpload" />

<AttributeTable Items="@GetAvatarAttributes()" Title="AvatarUpload" />
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Components;
using BootstrapBlazor.Shared.Common;
using BootstrapBlazor.Shared.Pages.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace BootstrapBlazor.Shared.Pages
{
    /// <summary>
    /// 
    /// </summary>
    public sealed partial class Uploads : IDisposable
    {
        private static readonly Random random = new Random();

        [Inject]
        [NotNull]
        private ToastService? ToastService { get; set; }

        [Inject]
        [NotNull]
        private IOptions<WebsiteOptions>? SiteOptions { get; set; }

        private Logger? Trace { get; set; }

        private IEnumerable<UploadFile> DefaultFormatFileList { get; } = new List<UploadFile>()
        {
            new UploadFile { FileName = "Test.xls" },
            new UploadFile { FileName = "Test.doc" },
            new UploadFile { FileName = "Test.ppt" },
            new UploadFile { FileName = "Test.mp3" },
            new UploadFile { FileName = "Test.mp4" },
            new UploadFile { FileName = "Test.pdf" },
            new UploadFile { FileName = "Test.cs" },
            new UploadFile { FileName = "Test.zip" },
            new UploadFile { FileName = "Test.txt" },
            new UploadFile { FileName = "Test.dat" }
        };

        private Task OnFileChange(UploadFile file)
        {
            // 未真正保存文件
            // file.SaveToFile()
            Trace?.Log($"{file.File!.Name} 上传成功");
            return Task.FromResult("");
        }

        private async Task OnClickToUpload(UploadFile file)
        {
            // 示例代码,模拟 80% 几率保存成功
            var error = random.Next(1, 100) > 80;
            if (error)
            {
                file.Code = 1;
                file.Error = "模拟上传失败";
            }
            else
            {
                await SaveToFile(file);
            }
        }

        private CancellationTokenSource? UploadFolderToken { get; set; }
        private async Task OnUploadFolder(UploadFile file)
        {
            // 上传文件夹时会多次回调此方法
            await SaveToFile(file);
        }

        private CancellationTokenSource? ReadAvatarToken { get; set; }
        private async Task OnAvatarUpload(UploadFile file)
        {
            // 示例代码,使用 base64 格式
            if (file != null && file.File != null)
            {
                var format = file.File.ContentType;
                if (CheckValidAvatarFormat(format))
                {
                    ReadAvatarToken ??= new CancellationTokenSource();
                    if (ReadAvatarToken.IsCancellationRequested)
                    {
                        ReadAvatarToken.Dispose();
                        ReadAvatarToken = new CancellationTokenSource();
                    }

                    await file.RequestBase64ImageFileAsync(format, 640, 480, MaxFileLength, ReadAvatarToken.Token);
                }
                else
                {
                    file.Code = 1;
                    file.Error = "文件格式不正确";
                }

                if (file.Code != 0)
                {
                    await ToastService.Error("头像上传", $"{file.Error} {format}");
                }
            }
        }

        private CancellationTokenSource? ReadToken { get; set; }

        private static long MaxFileLength => 200 * 1024 * 1024;

        private async Task OnCardUpload(UploadFile file)
        {
            if (file != null && file.File != null)
            {
                // 服务器端验证当文件大于 2MB 时提示文件太大信息
                if (file.Size > MaxFileLength)
                {
                    await ToastService.Information("上传文件", $"文件大小超过 200MB");
                    file.Code = 1;
                    file.Error = "文件大小超过 200MB";
                }
                else
                {
                    await SaveToFile(file);
                }
            }
        }

        private async Task<bool> SaveToFile(UploadFile file)
        {
            // Server Side 使用
            // Web Assembly 模式下必须使用 webapi 方式去保存文件到服务器或者数据库中
            // 生成写入文件名称
            var ret = false;
            if (!string.IsNullOrEmpty(SiteOptions.Value.WebRootPath))
            {
                var uploaderFolder = Path.Combine(SiteOptions.Value.WebRootPath, $"images{Path.DirectorySeparatorChar}uploader");
                file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
                var fileName = Path.Combine(uploaderFolder, file.FileName);

                ReadToken ??= new CancellationTokenSource();
                ret = await file.SaveToFile(fileName, MaxFileLength, ReadToken.Token);

                if (ret)
                {
                    // 保存成功
                    file.PrevUrl = $"images/uploader/{file.FileName}";
                }
                else
                {
                    await ToastService.Error("上传文件", $"保存文件失败 {file.OriginFileName}");
                }
            }
            else
            {
                await ToastService.Information("保存文件", "当前模式为 WebAssembly 模式,请调用 Webapi 模式保存文件到服务器端或数据库中");
            }
            return ret;
        }

        private static bool CheckValidAvatarFormat(string format)
        {
            return "jpg;png;bmp;gif;jpeg".Split(';').Any(f => format.Contains(f, StringComparison.OrdinalIgnoreCase));
        }

        private Task<bool> OnFileDelete(string fileName)
        {
            Trace?.Log($"{fileName} 成功移除");
            return Task.FromResult(true);
        }

        [NotNull]
        private Person? Foo { get; set; } = new Person();

        private static Task OnSubmit(EditContext context)
        {
            return Task.CompletedTask;
        }

        private class Person
        {
            [Display(Name = "姓名")]
            [Required]
            [StringLength(20, MinimumLength = 2)]
            public string Name { get; set; } = "Blazor";

            [Display(Name = "上传文件")]
            [Required]
            [FileValidation(Extensions = new string[] { ".png", ".jpg", ".jpeg" }, FileSize = 50 * 1024)]
            public IBrowserFile? Picture { get; set; }
        }

        /// <summary>
        /// 获得属性方法
        /// </summary>
        /// <returns></returns>
        private static IEnumerable<AttributeItem> GetInputAttributes() => new AttributeItem[]
        {
            new AttributeItem() {
                Name = "ShowDeleteButton",
                Description = "是否显示删除按钮",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsDisabled",
                Description = "是否禁用",
                Type = "boolean",
                ValueList = "true / false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "PlaceHolder",
                Description = "占位字符串",
                Type = "string",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "Accept",
                Description = "上传接收的文件格式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "BrowserButtonClass",
                Description = "上传按钮样式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-primary"
            },
            new AttributeItem() {
                Name = "BrowserButtonIcon",
                Description = "浏览按钮图标",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-folder-open-o"
            },
            new AttributeItem() {
                Name = "BrowserButtonText",
                Description = "浏览按钮显示文字",
                Type = "string",
                ValueList = " — ",
                DefaultValue = ""
            },
            new AttributeItem() {
                Name = "DeleteButtonClass",
                Description = "删除按钮样式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-danger"
            },
            new AttributeItem() {
                Name = "DeleteButtonIcon",
                Description = "删除按钮图标",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-trash-o"
            },
            new AttributeItem() {
                Name = "DeleteButtonText",
                Description = "删除按钮文字",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "删除"
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = "点击删除按钮时回调此方法",
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = "点击浏览按钮时回调此方法",
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " - "
            },
        };

        private static IEnumerable<AttributeItem> GetButtonAttributes() => new AttributeItem[]
        {
            new AttributeItem() {
                Name = "IsDirectory",
                Description = "是否上传整个目录",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsMultiple",
                Description = "是否允许多文件上传",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsSingle",
                Description = "是否仅上传一次",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "ShowProgress",
                Description = "是否显示上传进度",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "Accept",
                Description = "上传接收的文件格式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "BrowserButtonClass",
                Description = "上传按钮样式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-primary"
            },
            new AttributeItem() {
                Name = "BrowserButtonIcon",
                Description = "浏览按钮图标",
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-folder-open-o"
            },
            new AttributeItem() {
                Name = "BrowserButtonText",
                Description = "浏览按钮显示文字",
                Type = "string",
                ValueList = " — ",
                DefaultValue = ""
            },
            new AttributeItem() {
                Name = "DefaultFileList",
                Description = "已上传文件集合",
                Type = "List<UploadFile>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnGetFileFormat",
                Description = "设置文件格式图标回调委托",
                Type = "Func<string, string>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = "点击删除按钮时回调此方法",
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = "点击浏览按钮时回调此方法",
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " - "
            },
        };

        private static IEnumerable<AttributeItem> GetAvatarAttributes() => new AttributeItem[]
        {
            new AttributeItem() {
                Name = "Width",
                Description = "预览框宽度",
                Type = "int",
                ValueList = " — ",
                DefaultValue = "0"
            },
            new AttributeItem() {
                Name = "Height",
                Description = "预览框高度",
                Type = "int",
                ValueList = "—",
                DefaultValue = "0"
            },
            new AttributeItem() {
                Name = "IsCircle",
                Description = "是否为圆形头像模式",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsSingle",
                Description = "是否仅上传一次",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "ShowProgress",
                Description = "是否显示上传进度",
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "Accept",
                Description = "上传接收的文件格式",
                Type = "string",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "DefaultFileList",
                Description = "已上传文件集合",
                Type = "List<UploadFile>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = "点击删除按钮时回调此方法",
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " - "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = "点击浏览按钮时回调此方法",
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " - "
            },
        };

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            UploadFolderToken?.Dispose();
            ReadAvatarToken?.Cancel();
            ReadToken?.Cancel();
            GC.SuppressFinalize(this);
        }
    }
}

B 站相关视频链接

暂无

交流群

QQ群: BootstrapAdmin & Blazor(795206915)欢迎加群讨论
An error has occurred. This application may no longer respond until reloaded. Reload