Skip to content

Commit 7e580f9

Browse files
authored
feat(Upload): redesign upload components (#6049)
1 parent e8d8aca commit 7e580f9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+3728
-2881
lines changed

exclusion.dic

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,7 @@ inputmode
114114
Totp
115115
otpauth
116116
Hotp
117+
univer
118+
rdkit
119+
webkitdirectory
120+
dotx
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@page "/upload-avatar"
2+
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
3+
@inject IStringLocalizer<UploadAvatars> Localizer
4+
@inject ToastService ToastService
5+
6+
<h3>@Localizer["UploadsTitle"]</h3>
7+
8+
<h4>@Localizer["UploadsSubTitle"]</h4>
9+
10+
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
11+
12+
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
13+
14+
<DemoBlock Title="@Localizer["AvatarUploadTitle"]"
15+
Introduction="@Localizer["AvatarUploadIntro"]"
16+
Name="Normal">
17+
<section ignore>
18+
<div class="row g-3">
19+
<div class="col-12 col-sm-6">
20+
<BootstrapInputGroup>
21+
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
22+
<Switch @bind-Value="@_isDisabled"></Switch>
23+
</BootstrapInputGroup>
24+
</div>
25+
<div class="col-12 col-sm-6">
26+
<BootstrapInputGroup>
27+
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
28+
<Switch @bind-Value="@_isMultiple"></Switch>
29+
</BootstrapInputGroup>
30+
</div>
31+
<div class="col-12 col-sm-6">
32+
<BootstrapInputGroup>
33+
<BootstrapInputGroupLabel DisplayText="IsUploadButtonAtFirst"></BootstrapInputGroupLabel>
34+
<Switch @bind-Value="@_isUploadButtonAtFirst"></Switch>
35+
</BootstrapInputGroup>
36+
</div>
37+
<div class="col-12 col-sm-6">
38+
<BootstrapInputGroup>
39+
<BootstrapInputGroupLabel DisplayText="IsCircle"></BootstrapInputGroupLabel>
40+
<Switch @bind-Value="@_isCircle"></Switch>
41+
</BootstrapInputGroup>
42+
</div>
43+
<div class="col-12 col-sm-6">
44+
<BootstrapInputGroup>
45+
<BootstrapInputGroupLabel DisplayText="BorderRadius"></BootstrapInputGroupLabel>
46+
<Slider @bind-Value="@_radius" Min="0" Max="49" UseInputEvent="true" IsDisabled="@(_isCircle == false)"></Slider>
47+
</BootstrapInputGroup>
48+
</div>
49+
</div>
50+
</section>
51+
<AvatarUpload TValue="string" IsMultiple="@_isMultiple" IsCircle="@_isCircle" BorderRadius="@RadiusString"
52+
IsDisabled="@_isDisabled"></AvatarUpload>
53+
</DemoBlock>
54+
55+
<DemoBlock Title="@Localizer["AvatarUploadAcceptTitle"]"
56+
Introduction="@Localizer["AvatarUploadAcceptIntro"]"
57+
Name="Accept">
58+
<AvatarUpload TValue="string" Accept="image/*" OnChange="@OnChange"></AvatarUpload>
59+
</DemoBlock>
60+
61+
<DemoBlock Title="@Localizer["AvatarUploadValidateTitle"]"
62+
Introduction="@Localizer["AvatarUploadValidateIntro"]"
63+
Name="ValidateForm">
64+
<ValidateForm Model="@_foo" OnValidSubmit="OnAvatarValidSubmit">
65+
<div class="row g-3">
66+
<div class="col-12">
67+
<BootstrapInput @bind-Value="@_foo.Name"></BootstrapInput>
68+
</div>
69+
<div class="col-12">
70+
<AvatarUpload @bind-Value="@_foo.Picture" IsMultiple="true" MaxFileCount="3"></AvatarUpload>
71+
</div>
72+
<div class="col-12">
73+
<Button ButtonType="@ButtonType.Submit" Text="@Localizer["AvatarUploadButtonText"]"></Button>
74+
</div>
75+
</div>
76+
</ValidateForm>
77+
</DemoBlock>
78+
79+
<AttributeTable Items="@GetAttributes()"></AttributeTable>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
using Microsoft.AspNetCore.Components.Forms;
7+
8+
namespace BootstrapBlazor.Server.Components.Samples;
9+
10+
/// <summary>
11+
/// AvatarUpload sample code
12+
/// </summary>
13+
public partial class UploadAvatars : IDisposable
14+
{
15+
private static readonly long MaxFileLength = 5 * 1024 * 1024;
16+
private CancellationTokenSource? _token;
17+
private readonly List<UploadFile> _previewFileList = [];
18+
private readonly Person _foo = new();
19+
private bool _isUploadButtonAtFirst;
20+
private bool _isCircle;
21+
private int _radius = 49;
22+
private bool _isMultiple = true;
23+
private bool _isDisabled = false;
24+
25+
private string? RadiusString => $"{_radius}px";
26+
27+
/// <summary>
28+
/// <inheritdoc/>
29+
/// </summary>
30+
protected override void OnInitialized()
31+
{
32+
base.OnInitialized();
33+
34+
_previewFileList.AddRange(
35+
[
36+
new UploadFile { PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/Argo.png" }
37+
]);
38+
}
39+
40+
private async Task OnChange(UploadFile file)
41+
{
42+
// 示例代码,使用 base64 格式
43+
if (file is { File: not null })
44+
{
45+
var format = file.File.ContentType;
46+
if (file.IsImage())
47+
{
48+
_token ??= new CancellationTokenSource();
49+
if (_token.IsCancellationRequested)
50+
{
51+
_token.Dispose();
52+
_token = new CancellationTokenSource();
53+
}
54+
55+
await file.RequestBase64ImageFileAsync(format, 640, 480, MaxFileLength, null, _token.Token);
56+
}
57+
else
58+
{
59+
file.Code = 1;
60+
file.Error = Localizer["UploadsFormatError"];
61+
}
62+
63+
if (file.Code != 0)
64+
{
65+
await ToastService.Error(Localizer["UploadsAvatarMsg"], $"{file.Error} {format}");
66+
}
67+
}
68+
}
69+
70+
private Task OnAvatarValidSubmit(EditContext context)
71+
{
72+
return ToastService.Error(Localizer["UploadsValidateFormTitle"], Localizer["UploadsValidateFormValidContent"]);
73+
}
74+
75+
/// <summary>
76+
/// <inheritdoc/>
77+
/// </summary>
78+
public void Dispose()
79+
{
80+
_token?.Cancel();
81+
GC.SuppressFinalize(this);
82+
}
83+
84+
private List<AttributeItem> GetAttributes() =>
85+
[
86+
new()
87+
{
88+
Name = "Width",
89+
Description = Localizer["UploadsWidth"],
90+
Type = "int",
91+
ValueList = " — ",
92+
DefaultValue = "0"
93+
},
94+
new()
95+
{
96+
Name = "Height",
97+
Description = Localizer["UploadsHeight"],
98+
Type = "int",
99+
ValueList = " — ",
100+
DefaultValue = " — "
101+
},
102+
new()
103+
{
104+
Name = "IsCircle",
105+
Description = Localizer["UploadsIsCircle"],
106+
Type = "bool",
107+
ValueList = "true|false",
108+
DefaultValue = "false"
109+
},
110+
new()
111+
{
112+
Name = "BorderRadius",
113+
Description = Localizer["UploadsBorderRadius"],
114+
Type = "string?",
115+
ValueList = " — ",
116+
DefaultValue = " — "
117+
}
118+
];
119+
120+
class Person
121+
{
122+
[Required]
123+
[StringLength(20, MinimumLength = 2)]
124+
public string Name { get; set; } = "Blazor";
125+
126+
[Required]
127+
[FileValidation(Extensions = [".png", ".jpg", ".jpeg"], FileSize = 5 * 1024 * 1024)]
128+
public List<IBrowserFile>? Picture { get; set; }
129+
}
130+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@page "/upload-button"
2+
@inject IOptionsMonitor<WebsiteOptions> WebsiteOption
3+
@inject IStringLocalizer<UploadButtons> Localizer
4+
@inject ToastService ToastService
5+
6+
<h3>@Localizer["UploadsTitle"]</h3>
7+
8+
<h4>@Localizer["UploadsSubTitle"]</h4>
9+
10+
<p>@((MarkupString)Localizer["UploadsNote"].Value)</p>
11+
12+
<Pre class="no-highlight">builder.Services.Configure&lt;HubOptions&gt;(option => option.MaximumReceiveMessageSize = null);</Pre>
13+
14+
<DemoBlock Title="@Localizer["ButtonUploadTitle"]"
15+
Introduction="@Localizer["ButtonUploadIntro"]"
16+
Name="Normal">
17+
<section ignore>
18+
<div class="row g-3">
19+
<div class="col-12 col-sm-4">
20+
<BootstrapInputGroup>
21+
<BootstrapInputGroupLabel DisplayText="IsDisabled"></BootstrapInputGroupLabel>
22+
<Switch @bind-Value="@_isDisabled"></Switch>
23+
</BootstrapInputGroup>
24+
</div>
25+
<div class="col-12 col-sm-4">
26+
<BootstrapInputGroup>
27+
<BootstrapInputGroupLabel DisplayText="IsMultiple"></BootstrapInputGroupLabel>
28+
<Switch @bind-Value="@_isMultiple"></Switch>
29+
</BootstrapInputGroup>
30+
</div>
31+
<div class="col-12 col-sm-4">
32+
<BootstrapInputGroup>
33+
<BootstrapInputGroupLabel DisplayText="IsDirectory"></BootstrapInputGroupLabel>
34+
<Switch @bind-Value="@_isDirectory"></Switch>
35+
</BootstrapInputGroup>
36+
</div>
37+
<div class="col-12 col-sm-4">
38+
<BootstrapInputGroup>
39+
<BootstrapInputGroupLabel DisplayText="ShowProgress"></BootstrapInputGroupLabel>
40+
<Switch @bind-Value="@_showProgress"></Switch>
41+
</BootstrapInputGroup>
42+
</div>
43+
<div class="col-12 col-sm-4">
44+
<BootstrapInputGroup>
45+
<BootstrapInputGroupLabel DisplayText="ShowUploadFileList"></BootstrapInputGroupLabel>
46+
<Switch @bind-Value="@_showUploadFileList"></Switch>
47+
</BootstrapInputGroup>
48+
</div>
49+
<div class="col-12 col-sm-4">
50+
<BootstrapInputGroup>
51+
<BootstrapInputGroupLabel DisplayText="ShowDownloadButton"></BootstrapInputGroupLabel>
52+
<Switch @bind-Value="@_showDownloadButton"></Switch>
53+
</BootstrapInputGroup>
54+
</div>
55+
</div>
56+
</section>
57+
<ButtonUpload TValue="string" IsMultiple="@_isMultiple" IsDirectory="@_isDirectory" IsDisabled="@_isDisabled"
58+
ShowProgress="@_showProgress"
59+
ShowUploadFileList="@_showUploadFileList" ShowDownloadButton="@_showDownloadButton"
60+
OnChange="@OnClickToUpload" OnDownload="OnDownload" OnDelete="OnDelete"></ButtonUpload>
61+
</DemoBlock>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License
3+
// See the LICENSE file in the project root for more information.
4+
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
5+
6+
namespace BootstrapBlazor.Server.Components.Samples;
7+
8+
/// <summary>
9+
/// ButtonUpload sample code
10+
/// </summary>
11+
public partial class UploadButtons : IDisposable
12+
{
13+
private static readonly Random Random = new();
14+
private static readonly long MaxFileLength = 5 * 1024 * 1024;
15+
private CancellationTokenSource? _token;
16+
17+
private bool _isMultiple = true;
18+
private bool _showProgress = true;
19+
private bool _showUploadFileList = true;
20+
private bool _showDownloadButton = true;
21+
private bool _isDirectory = false;
22+
private bool _isDisabled = false;
23+
24+
private async Task OnClickToUpload(UploadFile file)
25+
{
26+
// 示例代码,模拟 80% 几率保存成功
27+
var error = Random.Next(1, 100) > 80;
28+
if (error)
29+
{
30+
file.Code = 1;
31+
file.Error = Localizer["UploadsError"];
32+
}
33+
else
34+
{
35+
await SaveToFile(file);
36+
}
37+
}
38+
39+
private async Task OnDownload(UploadFile item)
40+
{
41+
await ToastService.Success("文件下载", $"下载 {item.FileName} 成功");
42+
}
43+
44+
private async Task<bool> OnDelete(UploadFile item)
45+
{
46+
await ToastService.Success("文件操作", $"删除文件 {item.FileName} 成功");
47+
return true;
48+
}
49+
50+
private async Task SaveToFile(UploadFile file)
51+
{
52+
// Server Side 使用
53+
// Web Assembly 模式下必须使用 WebApi 方式去保存文件到服务器或者数据库中
54+
// 生成写入文件名称
55+
if (!string.IsNullOrEmpty(WebsiteOption.CurrentValue.WebRootPath))
56+
{
57+
var uploaderFolder = Path.Combine(WebsiteOption.CurrentValue.WebRootPath, "images", "uploader");
58+
file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
59+
var fileName = Path.Combine(uploaderFolder, file.FileName);
60+
61+
_token ??= new CancellationTokenSource();
62+
try
63+
{
64+
var ret = await file.SaveToFileAsync(fileName, MaxFileLength, _token.Token);
65+
66+
if (ret)
67+
{
68+
// 保存成功
69+
file.PrevUrl = $"{WebsiteOption.CurrentValue.AssetRootPath}images/uploader/{file.FileName}";
70+
}
71+
else
72+
{
73+
var errorMessage = $"{Localizer["UploadsSaveFileError"]} {file.OriginFileName}";
74+
file.Code = 1;
75+
file.Error = errorMessage;
76+
await ToastService.Error(Localizer["UploadFile"], errorMessage);
77+
}
78+
}
79+
catch (OperationCanceledException)
80+
{
81+
82+
}
83+
}
84+
else
85+
{
86+
file.Code = 1;
87+
file.Error = Localizer["UploadsWasmError"];
88+
await ToastService.Information(Localizer["UploadsSaveFile"], Localizer["UploadsSaveFileMsg"]);
89+
}
90+
}
91+
92+
/// <summary>
93+
/// <inheritdoc/>
94+
/// </summary>
95+
public void Dispose()
96+
{
97+
_token?.Cancel();
98+
_token?.Dispose();
99+
_token = null;
100+
GC.SuppressFinalize(this);
101+
}
102+
}

0 commit comments

Comments
 (0)