diff --git a/doc/TDesign.Docs.ServerSide/Controllers/UploadController.cs b/doc/TDesign.Docs.ServerSide/Controllers/UploadController.cs new file mode 100644 index 00000000..836af067 --- /dev/null +++ b/doc/TDesign.Docs.ServerSide/Controllers/UploadController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Mvc; + +namespace TDesign.Docs.ServerSide.Controllers; +[ApiController] +[Route("api/[controller]")] +public class UploadController:ControllerBase +{ + [HttpPost("file")] + public async Task PostAsync([FromForm]List files) + { + //var savedPath = "/upload"; + //var generateFileName = Path.Combine(DateTimeOffset.UnixEpoch.ToString(), Path.GetExtension(file.Name)); + + //if ( !Directory.Exists(savedPath) ) + //{ + // Directory.CreateDirectory(savedPath); + //} + + //var serverFilePath = Path.Combine(savedPath, generateFileName); + + //using var fileStream = new FileStream(serverFilePath, FileMode.CreateNew); + //await file.OpenReadStream().CopyToAsync(fileStream); + + return Ok(); + } +} diff --git a/doc/TDesign.Docs.ServerSide/TDesign.xml b/doc/TDesign.Docs.ServerSide/TDesign.xml index f84e9b85..aab0cbf6 100644 --- a/doc/TDesign.Docs.ServerSide/TDesign.xml +++ b/doc/TDesign.Docs.ServerSide/TDesign.xml @@ -1747,6 +1747,210 @@ + + + 上传组件允许用户传输文件或提交自己的内容。 + + + + + 上传文件在表单中的名称。 + + + + + 批量上传 + + + + + 是否在选择文件后自动发起请求上传文件。 + + + + + 上传的风格样式。 + + + + + 上传的显示文本。 + + + + + 当前最大上传文件数量。默认 100。 + + + + + 接受上传的文件类型。 + + 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept + + + + + + 设置服务端上传的 API 地址。要求 API 可以通过 POST 方式接收请求,并支持 FORM DATA 数据传输方式。 + + + + + 单个上传的文件限制大小,单位 B。默认 512KB。 + + + + + 正在上传 + + + + + 上传中的显示 + + + 文件信息。 + + + + 上传后的显示 + + + 文件信息。 + + + + 处理要上传的文件。 + + 要上传的文件列表。 + + + + + 上传组件显示的风格样式。 + + + + + 默认的文件上传风格。 + + + + + 输入框形式的文件上传风格。 + + + + + 要上传的文件信息。 + + + + + 获取文件的编号。 + + + + + 获取只包含文件名,不包含任何路径信息的文件名称。 + + + + + 获取字节数为单位的文件大小。 + + + + + 获取文件的 MIME 类型。 + + + + + 获取文件对象的 URL 地址。 + + + + + 获取或设置上传以后的文件完整路径。 + + + + + 获取或设置上传后的结果状态。 + + + + + 表示文件上传后的结果。 + + + + + 获取或设置上传的文件名。 + + + + + 获取或设置上传以后的文件完整路径。 + + + + + 获取或设置上传后的文件大小。 + + + + + 获取或设置上传后的结果状态。 + + + + + 获取或设置上传后的提示 + + + + + 上传用的参数。 + + + + + 文件索引编号。 + + + + + 后端上传地址。 + + + + + form/data 文件的名称。 + + + + + 文件的Id。 + + + + + 获取或设置 HTTP 提交方式。 + + + + + 附带的 Header + + + + + 附带的 form/data 字段。 + + 表示栅格的列。 @@ -5133,109 +5337,6 @@ 设置主题颜色。 - - - 上传组件允许用户传输文件或提交自己的内容。 - - - - - 批量上传 - - - - - 是否在选择文件后自动发起请求上传文件。 - - - - - 上传的风格样式。 - - - - - 当前最大上传文件数量。默认 100。 - - - - - 接受上传的文件类型。 - - 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept - - - - - - 上传组件显示的风格样式。 - - - - - 默认的文件上传风格。 - - - - - 输入框形式的文件上传风格。 - - - - - 文件批量上传。 - - - - - 默认图片上传风格。 - - - - - 图片批量上传风格。 - - - - - 完全自定义风格。 - - - - - 表示文件上传后的结果。 - - - - - 获取或设置上传后的文件名。 - - - - - 获取或设置上传后的文件大小。 - - - - - 获取或设置上传后的时间。 - - - - - 获取或设置上传后的结果状态。 - - - - - 获取或设置上传后的提示 - - - - - 获取 - - 水平对齐方式。 diff --git a/doc/TDesign.Docs.Shared/Pages/Components/Input/UploadPage.razor b/doc/TDesign.Docs.Shared/Pages/Components/Input/UploadPage.razor index ec5e97cd..3d71aa21 100644 --- a/doc/TDesign.Docs.Shared/Pages/Components/Input/UploadPage.razor +++ b/doc/TDesign.Docs.Shared/Pages/Components/Input/UploadPage.razor @@ -9,7 +9,8 @@ - + + @Code.Create(@" diff --git a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml index f84e9b85..aab0cbf6 100644 --- a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml +++ b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml @@ -1747,6 +1747,210 @@ + + + 上传组件允许用户传输文件或提交自己的内容。 + + + + + 上传文件在表单中的名称。 + + + + + 批量上传 + + + + + 是否在选择文件后自动发起请求上传文件。 + + + + + 上传的风格样式。 + + + + + 上传的显示文本。 + + + + + 当前最大上传文件数量。默认 100。 + + + + + 接受上传的文件类型。 + + 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept + + + + + + 设置服务端上传的 API 地址。要求 API 可以通过 POST 方式接收请求,并支持 FORM DATA 数据传输方式。 + + + + + 单个上传的文件限制大小,单位 B。默认 512KB。 + + + + + 正在上传 + + + + + 上传中的显示 + + + 文件信息。 + + + + 上传后的显示 + + + 文件信息。 + + + + 处理要上传的文件。 + + 要上传的文件列表。 + + + + + 上传组件显示的风格样式。 + + + + + 默认的文件上传风格。 + + + + + 输入框形式的文件上传风格。 + + + + + 要上传的文件信息。 + + + + + 获取文件的编号。 + + + + + 获取只包含文件名,不包含任何路径信息的文件名称。 + + + + + 获取字节数为单位的文件大小。 + + + + + 获取文件的 MIME 类型。 + + + + + 获取文件对象的 URL 地址。 + + + + + 获取或设置上传以后的文件完整路径。 + + + + + 获取或设置上传后的结果状态。 + + + + + 表示文件上传后的结果。 + + + + + 获取或设置上传的文件名。 + + + + + 获取或设置上传以后的文件完整路径。 + + + + + 获取或设置上传后的文件大小。 + + + + + 获取或设置上传后的结果状态。 + + + + + 获取或设置上传后的提示 + + + + + 上传用的参数。 + + + + + 文件索引编号。 + + + + + 后端上传地址。 + + + + + form/data 文件的名称。 + + + + + 文件的Id。 + + + + + 获取或设置 HTTP 提交方式。 + + + + + 附带的 Header + + + + + 附带的 form/data 字段。 + + 表示栅格的列。 @@ -5133,109 +5337,6 @@ 设置主题颜色。 - - - 上传组件允许用户传输文件或提交自己的内容。 - - - - - 批量上传 - - - - - 是否在选择文件后自动发起请求上传文件。 - - - - - 上传的风格样式。 - - - - - 当前最大上传文件数量。默认 100。 - - - - - 接受上传的文件类型。 - - 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept - - - - - - 上传组件显示的风格样式。 - - - - - 默认的文件上传风格。 - - - - - 输入框形式的文件上传风格。 - - - - - 文件批量上传。 - - - - - 默认图片上传风格。 - - - - - 图片批量上传风格。 - - - - - 完全自定义风格。 - - - - - 表示文件上传后的结果。 - - - - - 获取或设置上传后的文件名。 - - - - - 获取或设置上传后的文件大小。 - - - - - 获取或设置上传后的时间。 - - - - - 获取或设置上传后的结果状态。 - - - - - 获取或设置上传后的提示 - - - - - 获取 - - 水平对齐方式。 diff --git a/src/TDesign/Components/Forms/Upload/TUpload.cs b/src/TDesign/Components/Forms/Upload/TUpload.cs new file mode 100644 index 00000000..e84ccc59 --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/TUpload.cs @@ -0,0 +1,240 @@ +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace TDesign; + +/// +/// 上传组件允许用户传输文件或提交自己的内容。 +/// +[CssClass("t-upload")] +public partial class TUpload : TDesignComponentBase +{ + /// + /// 上传文件在表单中的名称。 + /// + [Parameter] public string Name { get; set; } = "file"; + /// + /// 批量上传 + /// + [Parameter] public bool Multiple { get; set; } + /// + /// 是否在选择文件后自动发起请求上传文件。 + /// + [Parameter] public bool AutoUpload { get; set; } = true; + /// + /// 上传的风格样式。 + /// + [Parameter] public UploadTheme Theme { get; set; } = UploadTheme.File; + /// + /// 上传的显示文本。 + /// + [Parameter] public string Text { get; set; } = "选择文件"; + + /// + /// 当前最大上传文件数量。默认 100。 + /// + [Parameter] public int Max { get; set; } = 100; + /// + /// 接受上传的文件类型。 + /// + /// 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept + /// + /// + [Parameter] public string? Accept { get; set; } + /// + /// 设置服务端上传的 API 地址。要求 API 可以通过 POST 方式接收请求,并支持 FORM DATA 数据传输方式。 + /// + [Parameter][EditorRequired] public string Action { get; set; } + /// + /// 单个上传的文件限制大小,单位 B。默认 512KB。 + /// + [Parameter] public long Size { get; set; } = 512000; + + [Parameter] public Theme ButtonTheme { get; set; } = TDesign.Theme.Primary; + [Parameter] public object? ButtonIcon { get; set; } = IconName.Upload; + + + + private List _fileList = new(); + + [Inject] IJSRuntime JS { get; set; } + [Inject] HttpClient Client { get; set; } + + ElementReference? RefInputFile; + private IJSModule _uploadJSModule; + + /// + /// 正在上传 + /// + private bool _isUploading; + + protected override void OnParametersSet() + { + if (string.IsNullOrEmpty(Action)) + { + throw new InvalidOperationException($"没有设置{nameof(Action)}参数"); + } + + base.OnParametersSet(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _uploadJSModule = await JS.ImportTDesignModuleAsync("upload"); + } + } + + protected override void AddContent(RenderTreeBuilder builder, int sequence) + { + builder.Element("input") + .Ref(e => RefInputFile = e) + .Attribute("type","file") + .Attribute("name",Name) + .Attribute("multiple", "multiple", Multiple) + .Attribute("hidden", "hidden") + .Close(); + + BuildFile(builder); + } + + protected override void BuildCssClass(ICssClassBuilder builder) + { + builder.Append("t-upload--theme-file-input", Theme == UploadTheme.FileInput); + } + + void BuildFile(RenderTreeBuilder builder) + { + if (!new[] { UploadTheme.File, UploadTheme.FileInput }.Contains(Theme)) + { + return; + } + + builder.Div("t-upload__single") + .Class($"t-upload__{Theme.GetCssClass()}") + .Content(content => + { + if (Theme == UploadTheme.FileInput) + { + + } + + BuildTrigger(content); + BuildDisplayText(content); + }) + .Close(); + + } + + void BuildTrigger(RenderTreeBuilder builder) + { + builder.Div("t-upload__trigger") + .Content(button => + { + button.Component() + .Attribute(m => m.Theme, ButtonTheme) + .Attribute(m => m.Icon, ButtonIcon) + .Attribute(m => m.OnClick, HtmlHelper.Instance.Callback().Create(this, async e => + { + await _uploadJSModule.Module.InvokeVoidAsync("upload.showDialog", RefInputFile, JSInvokeMethodFactory.Create,Task>(HandleUploadingFiles)); + })) + .Content(Text) + .Close(); + }) + .Close(); + } + + void BuildDisplayText(RenderTreeBuilder builder) + { + foreach (var item in _fileList) + { + + builder.Div("t-upload__single-display-text t-upload__display-text--margin") + .Content(content => + { + if ( item.Percent < 100 )//上传中 + { + BuildUploadingDisplayContent(content, item); + } + else + { + BuildUploadedDisplayContent(content, item); + } + }) + .Close(); + } + } + + + /// + /// 上传中的显示 + /// + /// + /// 文件信息。 + void BuildUploadingDisplayContent(RenderTreeBuilder builder, UploadFileInfo fileInfo) + { + builder.Span("t-upload__single-name").Content(fileInfo.Name).Close(); + builder.Div("t-upload__single-progress") + .Content(loader => + { + loader.Component().Attribute(m => m.Center, true).Close(); + loader.Span("t-upload__single-percent").Content($"{fileInfo.Percent}%").Close(); + }) + .Close(); + } + + /// + /// 上传后的显示 + /// + /// + /// 文件信息。 + void BuildUploadedDisplayContent(RenderTreeBuilder builder, UploadFileInfo fileInfo) + { + builder.Component() + .Attribute(m => m.Size, TDesign.Size.Small) + .Attribute(m => m.Hover, LinkHover.Color) + .Content(fileInfo.Name) + .Close(); + + builder.Component().Attribute(m => m.Name, IconName.Close).Close(); + } + + + protected override void DisposeComponentResources() + { + Client.Dispose(); + } +} +/// +/// 上传组件显示的风格样式。 +/// +public enum UploadTheme +{ + /// + /// 默认的文件上传风格。 + /// + [CssClass("single-file")] File, + /// + /// 输入框形式的文件上传风格。 + /// + [CssClass("single-file-input")] FileInput, + ///// + ///// 文件批量上传。 + ///// + //[CssClass("flow-file-flow")]FileFlow, + ///// + ///// 默认图片上传风格。 + ///// + //Image, + ///// + ///// 图片批量上传风格。 + ///// + //[CssClass("flow-image-flow")]ImageFlow, + ///// + ///// 完全自定义风格。 + ///// + //Custom, +} \ No newline at end of file diff --git a/src/TDesign/Components/Forms/Upload/TUpload.method.cs b/src/TDesign/Components/Forms/Upload/TUpload.method.cs new file mode 100644 index 00000000..eff42d27 --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/TUpload.method.cs @@ -0,0 +1,67 @@ +using Microsoft.JSInterop; + +namespace TDesign; +partial class TUpload +{ + /// + /// 处理要上传的文件。 + /// + /// 要上传的文件列表。 + /// + async Task HandleUploadingFiles(IReadOnlyList files) + { + int index = 0; + foreach ( var file in files.Where(f=>f is not null) ) + { + _fileList.Add(file); + var parameterInfo = new UploadParameter + { + Index = index, + ActionUrl = Action, + Name = Name, + FileId = file.Id, + }; + + await _uploadJSModule.Module.InvokeVoidAsync("upload.uploadFile", RefInputFile, parameterInfo, DotNetObjectReference.Create(this)); + + index++; + } + } + + + [JSInvokable("onSuccess")] + public async Task OnSuccessAsync(Guid fileId, string responseText) + { + var fileInfo = GetFileInfo(fileId); + + fileInfo.Status = Status.Success; + fileInfo.Percent = 100; + fileInfo.ResponseText = responseText; + await this.Refresh(); + } + + [JSInvokable("onError")] + public async Task OnErrorAsync(Guid fileId, string responseText) + { + var fileInfo = GetFileInfo(fileId); + + fileInfo.Status = Status.Error; + fileInfo.Percent = 100; + fileInfo.ResponseText = responseText; + await this.Refresh(); + } + + [JSInvokable("onProgress")] + public async Task OnProgressAsync(Guid fileId, int percent) + { + var fileInfo = GetFileInfo(fileId); + fileInfo.Percent = percent; + await this.Refresh(); + } + + UploadFileInfo GetFileInfo(Guid id) + { + var fileInfo = _fileList.Find(m => m.Id == id); + return fileInfo ?? throw new InvalidOperationException($"无法获取到上传的文件 id ({id})"); + } +} diff --git a/src/TDesign/Components/Forms/Upload/UploadFileInfo.cs b/src/TDesign/Components/Forms/Upload/UploadFileInfo.cs new file mode 100644 index 00000000..1e2183be --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/UploadFileInfo.cs @@ -0,0 +1,66 @@ +using Microsoft.JSInterop; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TDesign; + +/// +/// 要上传的文件信息。 +/// +public class UploadFileInfo +{ + public UploadFileInfo(string name, long size, string contentType, string? objectUrl) + { + Name = name; + Size = size; + ContentType = contentType; + ObjectUrl = objectUrl; + + Id = Guid.NewGuid(); + } + /// + /// 获取文件的编号。 + /// + public Guid Id { get; } + /// + /// 获取只包含文件名,不包含任何路径信息的文件名称。 + /// + public string Name { get; } + /// + /// 获取字节数为单位的文件大小。 + /// + public long Size { get; } + /// + /// 获取文件的 MIME 类型。 + /// + public string? ContentType { get; } + + /// + /// 获取文件对象的 URL 地址。 + /// + public string? ObjectUrl { get; } + + /// + /// 获取或设置上传以后的文件完整路径。 + /// + public string? UploadedFilePath { get;internal set; } + /// + /// 获取或设置上传后的结果状态。 + /// + public Status Status { get;internal set; } = Status.Default; + + public int Percent { get; internal set; } + + + internal string ResponseText { get; set; } + + public TResult? GetResponse(JsonSerializerOptions? jsonSerializerOptions=default) + { + jsonSerializerOptions ??= new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + return JsonSerializer.Deserialize(ResponseText, jsonSerializerOptions); + } +} diff --git a/src/TDesign/Components/Forms/Upload/UploadFileResult.cs b/src/TDesign/Components/Forms/Upload/UploadFileResult.cs new file mode 100644 index 00000000..fbc4060b --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/UploadFileResult.cs @@ -0,0 +1,28 @@ +namespace TDesign; + +/// +/// 表示文件上传后的结果。 +/// +public class UploadFileResult +{ + /// + /// 获取或设置上传的文件名。 + /// + public string? OriginalFileName { get; set; } + /// + /// 获取或设置上传以后的文件完整路径。 + /// + public string? UploadedFilePath { get; set; } + /// + /// 获取或设置上传后的文件大小。 + /// + public long? FileSize { get; set; } + /// + /// 获取或设置上传后的结果状态。 + /// + public Status Status { get; set; } = Status.Default; + /// + /// 获取或设置上传后的提示 + /// + public string? Message { get; set; } +} diff --git a/src/TDesign/Components/Forms/Upload/UploadHttpResponse.cs b/src/TDesign/Components/Forms/Upload/UploadHttpResponse.cs new file mode 100644 index 00000000..81119e25 --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/UploadHttpResponse.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace TDesign; +public class UploadHttpResponse +{ + public UploadHttpResponse(int statusCode, string? statusText, string? responseText) + { + StatusCode = statusCode; + StatusText = statusText; + ResponseText = responseText; + } + + [JsonPropertyName("status")] + public int StatusCode { get; } + + [JsonPropertyName("statusText")] + public string? StatusText { get; } + + [JsonPropertyName("responseText")] + public string? ResponseText { get; } +} diff --git a/src/TDesign/Components/Forms/Upload/UploadParameter.cs b/src/TDesign/Components/Forms/Upload/UploadParameter.cs new file mode 100644 index 00000000..d024f494 --- /dev/null +++ b/src/TDesign/Components/Forms/Upload/UploadParameter.cs @@ -0,0 +1,40 @@ +using Microsoft.JSInterop; + +namespace TDesign; + +/// +/// 上传用的参数。 +/// +internal class UploadParameter +{ + /// + /// 文件索引编号。 + /// + public int Index { get; init; } + /// + /// 后端上传地址。 + /// + public string ActionUrl { get; init; } + /// + /// form/data 文件的名称。 + /// + public string? Name { get; init; } + + /// + /// 文件的Id。 + /// + public Guid FileId { get;init; } + /// + /// 获取或设置 HTTP 提交方式。 + /// + public string Method => "POST"; + /// + /// 附带的 Header + /// + public Dictionary Headers { get; set; } = new(); + /// + /// 附带的 form/data 字段。 + /// + public Dictionary Data { get; set; } = new(); + +} diff --git a/src/TDesign/Components/TUpload.cs b/src/TDesign/Components/TUpload.cs deleted file mode 100644 index 8d57ec64..00000000 --- a/src/TDesign/Components/TUpload.cs +++ /dev/null @@ -1,282 +0,0 @@ -using ComponentBuilder.FluentRenderTree; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.JSInterop; -using System.Net.Http.Headers; -using System.Text.Json; - -namespace TDesign; - -/// -/// 上传组件允许用户传输文件或提交自己的内容。 -/// -[CssClass("t-upload")] -public class TUpload:TDesignComponentBase -{ - /// - /// 批量上传 - /// - [Parameter]public bool Multiple { get; set; } - /// - /// 是否在选择文件后自动发起请求上传文件。 - /// - [Parameter]public bool AutoUpload { get; set; } - /// - /// 上传的风格样式。 - /// - [Parameter] public UploadTheme Theme { get; set; } = UploadTheme.File; - /// - /// 上传的显示文本。 - /// - [Parameter] public string Text { get; set; } = "选择文件"; - - /// - /// 当前最大上传文件数量。默认 100。 - /// - [Parameter]public int Max { get; set;} = 100; - /// - /// 接受上传的文件类型。 - /// - /// 参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#accept - /// - /// - [Parameter]public string? Accept { get; set; } - /// - /// 设置服务端上传的 API 路径。 - /// - [Parameter][EditorRequired]public string Action { get; set; } - /// - /// 单个上传的文件限制大小,单位 B。默认 512KB。 - /// - [Parameter] public long Size { get; set; } = 512000; - - [Parameter] public Theme ButtonTheme { get; set; } = TDesign.Theme.Primary; - [Parameter] public object? ButtonIcon { get; set; } = IconName.Upload; - - private List _results = new(); - /// - /// 获取上传结果。 - /// - public IReadOnlyList UploadResults => _results; - - [Inject]IJSRuntime JS { get; set; } - [Inject]HttpClient Client { get; set; } - - InputFile? RefInputFile; - private IJSModule _uploadJSModule; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if ( firstRender ) - { - _uploadJSModule = await JS.ImportTDesignModuleAsync("upload"); - } - } - - protected override void AddContent(RenderTreeBuilder builder, int sequence) - { - builder.Component() - .Ref(e => RefInputFile = e) - .Attribute(m => m.OnChange, HtmlHelper.Instance.Callback().Create(this,async e=> - { - if ( AutoUpload ) - { - await Upload(e); - } - })) - .Attribute("multiple", "multiple", Multiple) - .Attribute("hidden", "hidden") - .Close(); - - BuildFile(builder); - } - - protected override void BuildCssClass(ICssClassBuilder builder) - { - builder.Append("t-upload--theme-file-input", Theme == UploadTheme.FileInput); - } - - void BuildFile(RenderTreeBuilder builder) - { - if(!new[] { UploadTheme.File, UploadTheme.FileInput }.Contains(Theme) ) - { - return; - } - - builder.Div("t-upload__single") - .Class($"t-upload__{Theme.GetCssClass()}") - .Content(content => - { - if(Theme== UploadTheme.FileInput ) - { - - } - - BuildTrigger(content); - }) - .Close(); - - } - - void BuildTrigger(RenderTreeBuilder builder) - { - builder.Div("t-upload__trigger") - .Content(button => - { - button.Component() - .Attribute(m=>m.Theme,ButtonTheme) - .Attribute(m=>m.Icon,ButtonIcon) - .Attribute(m => m.OnClick, HtmlHelper.Instance.Callback().Create(this, async e => - { - await _uploadJSModule.Module.InvokeVoidAsync("upload.openDialog", RefInputFile?.Element); - })) - .Content(Text) - .Close(); - }) - .Close(); - } - - void BuildDisplayText(RenderTreeBuilder builder) - { - builder.Div("t-upload__single-display-text t-upload__display-text--margin") - .Close(); - } - - private bool shouldRender; - - protected override bool ShouldRender() => shouldRender; - - /// - /// 执行上传的操作。 - /// - /// - public async Task Upload(InputFileChangeEventArgs e) - { - var browserFiles = new List(Max); - - try - { - browserFiles.AddRange(e.GetMultipleFiles(Max)); - } - catch(InvalidOperationException ex ) - { - //超过设定的文件数量如何处理 - return; - } - - shouldRender = false; - var readyToUpload = false; - - using var content = new MultipartFormDataContent(); - - foreach ( var file in browserFiles ) - { - var result = new UploadResult - { - FileName = file.Name, - FileSize = file.Size - }; - try - { - var fileContent = new StreamContent(file.OpenReadStream(Size)); - - fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); - content.Add(fileContent, "\"files\"", file.Name); - - readyToUpload = true; - } - catch ( IOException ex ) - { - result.Status = Status.Default; - result.Message = ex.Message; - } - finally - { - _results.Add(result); - } - } - - if ( readyToUpload )//可以上传到服务器 - { - var response = await Client.PostAsync(Action, content); - if ( response.IsSuccessStatusCode ) - { - using var readStream = await response.Content.ReadAsStreamAsync(); - - if ( readStream is not null ) - { - var serverUploadResults = await JsonSerializer.DeserializeAsync>(readStream, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - - if ( serverUploadResults is not null ) - { - _results.AddRange(serverUploadResults); - } - } - } - } - - shouldRender = true; - } - - protected override void DisposeComponentResources() - { - Client.Dispose(); - } -} -/// -/// 上传组件显示的风格样式。 -/// -public enum UploadTheme -{ - /// - /// 默认的文件上传风格。 - /// - [CssClass("single-file")]File, - /// - /// 输入框形式的文件上传风格。 - /// - [CssClass("single-file-input")]FileInput, - ///// - ///// 文件批量上传。 - ///// - //[CssClass("flow-file-flow")]FileFlow, - ///// - ///// 默认图片上传风格。 - ///// - //Image, - ///// - ///// 图片批量上传风格。 - ///// - //[CssClass("flow-image-flow")]ImageFlow, - ///// - ///// 完全自定义风格。 - ///// - //Custom, -} - -/// -/// 表示文件上传后的结果。 -/// -public class UploadResult -{ - /// - /// 获取或设置上传后的文件名。 - /// - public string? FileName { get; set; } - /// - /// 获取或设置上传后的文件大小。 - /// - public long? FileSize { get; set; } - /// - /// 获取或设置上传后的时间。 - /// - public DateTime? UploadedTime { get; set; } - - /// - /// 获取或设置上传后的结果状态。 - /// - public Status Status { get; set; } = Status.Default; - /// - /// 获取或设置上传后的提示 - /// - public string? Message { get; set; } -} \ No newline at end of file diff --git a/src/TDesign/wwwroot/libs/tdesign-blazor-upload.js b/src/TDesign/wwwroot/libs/tdesign-blazor-upload.js index b125119d..87ced63f 100644 --- a/src/TDesign/wwwroot/libs/tdesign-blazor-upload.js +++ b/src/TDesign/wwwroot/libs/tdesign-blazor-upload.js @@ -1,8 +1,76 @@ let upload = { - openDialog: function (inputElement) { - if (inputElement) { - inputElement.click(); + _files: null, + showDialog: function (inputElement, dotNetHelper) { + if (!inputElement) { + return; } + inputElement.click(); + + inputElement.addEventListener('change', e => { + this._files = e.target.files; + + let files = this._files; + let fileInfoList = new Array(files.length); //对应代码中的 UploadFileInfo 对象 + for (var i = 0; i < files.length; i++) { + let file = files[i]; + fileInfoList.push({ + name: file.name, + size: file.size, + contentType: file.type, + Url: URL.createObjectURL(file) + }); + } + + dotNetHelper.invokeMethodAsync("Invoke", fileInfoList); + }) + }, + uploadFile: function (inputElement, parameters, dotNetHelper) { + let formData = new FormData(); + + let file = this._files[parameters.index]; + let size = file.size; + formData.append(parameters.name, file); + + if (parameters.data) { + for (let key in parameters.data) { + formData.append(key, parameters.data[key]); + } + } + + const xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = (request, e) => { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + dotNetHelper.invokeMethodAsync("onSuccess", parameters.fileId, xhr.responseText); + } else { + dotNetHelper.invokeMethodAsync("onError", parameters.fileId, xhr.responseText); + } + } + }; + xhr.upload.onprogress = (e) => { + var percent = Math.floor(e.loaded / size * 100); + dotNetHelper.invokeMethodAsync("onProgress", parameters.fileId, percent); + }; + xhr.onerror = e => { + dotNetHelper.invokeMethodAsync("onError", parameters.fileId, xhr.responseText); + } + if (!parameters.method) { + parameters.method = "POST"; + } + if (!parameters.actionUrl) { + throw '必须提供上传的后端 URL'; + } + + xhr.open(parameters.method, parameters.actionUrl, true);//异步上传 + + if (parameters.headers) { + for (let key in parameters.headers) { + xhr.setRequestHeader(key, parameters.headers[key]); + } + } + + xhr.send(formData);//发送请求到后端 } }