From 47cbc26177652199285aea1daeb7d555724c501b Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 1 Aug 2025 17:18:20 +0800 Subject: [PATCH 01/25] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Key=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/BaseComponents/DynamicElement.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/BootstrapBlazor/Components/BaseComponents/DynamicElement.cs b/src/BootstrapBlazor/Components/BaseComponents/DynamicElement.cs index 53f30af13e3..39bde4e177b 100644 --- a/src/BootstrapBlazor/Components/BaseComponents/DynamicElement.cs +++ b/src/BootstrapBlazor/Components/BaseComponents/DynamicElement.cs @@ -80,6 +80,12 @@ public class DynamicElement : BootstrapComponentBase [Parameter] public bool GenerateElement { get; set; } = true; + /// + /// 获得/设置 组件 Key 值 + /// + [Parameter] + public object? Key { get; set; } + /// /// BuildRenderTree 方法 /// @@ -119,6 +125,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddContent(8, ChildContent); + if (Key != null) + { + builder.SetKey(Key); + } + if (GenerateElement || IsTriggerClick() || IsTriggerDoubleClick()) { builder.CloseElement(); From da3306280278609cd899f4ccc2e51198352abc0d Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 1 Aug 2025 17:20:10 +0800 Subject: [PATCH 02/25] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E6=96=B9=E6=B3=95=20GetKeyByITem=20=E8=8E=B7=E5=BE=97?= =?UTF-8?q?=E8=A1=8C=20Key=20=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Table/Table.razor | 4 ++-- src/BootstrapBlazor/Components/Table/Table.razor.cs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index 80d1bc0bed6..9687f30c3d0 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -237,7 +237,7 @@ } else { - ; RenderFragment RenderRow => item => - @ SortName != col.GetFieldName( [Parameter] public bool ShowColumnWidthTooltip { get; set; } + /// + /// 获得/设置 行 Key 回调方法 + /// + [Parameter] + public Func? OnGetRowKey { get; set; } + private string ScrollWidthString => $"width: {ActualScrollWidth}px;"; private string? GetScrollStyleString(bool condition) => condition @@ -1667,6 +1673,8 @@ private void OnTouchEnd() TouchStart = false; } + private object? GetKeyByITem(TItem item) => OnGetRowKey?.Invoke(item); + /// /// Dispose 方法 /// From e7dfe41dd8515b7a9d3ea344976bfd10f3ed6fa4 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 2 Aug 2025 10:10:24 +0800 Subject: [PATCH 03/25] chore: bump version 9.6.5 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 35916bc0d1c..a60e5c4b2e3 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.4 + 9.6.5 From 1a71e4c13814fb535a3751bd17a31bc426a69d1e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 27 Oct 2025 12:42:17 +0800 Subject: [PATCH 04/25] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20PdfOptions?= =?UTF-8?q?=20=E9=85=8D=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Options/PdfOptions.cs | 17 ++++++++++++ .../Services/DefaultHtml2PdfService.cs | 26 +++---------------- src/BootstrapBlazor/Services/IHtml2Pdf.cs | 12 ++++++--- 3 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 src/BootstrapBlazor/Options/PdfOptions.cs diff --git a/src/BootstrapBlazor/Options/PdfOptions.cs b/src/BootstrapBlazor/Options/PdfOptions.cs new file mode 100644 index 00000000000..23e744268bd --- /dev/null +++ b/src/BootstrapBlazor/Options/PdfOptions.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +/// +/// PdfOptions 实例用于设置导出 Pdf 相关选项 +/// +public class PdfOptions +{ + /// + /// 获得/设置 是否横向打印 默认 false + /// + public bool Landscape { get; set; } +} diff --git a/src/BootstrapBlazor/Services/DefaultHtml2PdfService.cs b/src/BootstrapBlazor/Services/DefaultHtml2PdfService.cs index aee8de4afe5..778e0ef9279 100644 --- a/src/BootstrapBlazor/Services/DefaultHtml2PdfService.cs +++ b/src/BootstrapBlazor/Services/DefaultHtml2PdfService.cs @@ -12,29 +12,11 @@ class DefaultHtml2PdfService : IHtml2Pdf { private const string ErrorMessage = "请增加依赖包 BootstrapBlazor.Html2Pdf 通过 AddBootstrapBlazorHtml2PdfService 进行服务注入; Please add BootstrapBlazor.Html2Pdf package and use AddBootstrapBlazorHtml2PdfService inject service"; - /// - /// - /// - public Task PdfDataAsync(string url) => throw new NotImplementedException(ErrorMessage); + public Task PdfDataAsync(string url, PdfOptions? options = null) => throw new NotImplementedException(ErrorMessage); - /// - /// - /// - public Task PdfStreamAsync(string url) => throw new NotImplementedException(ErrorMessage); + public Task PdfStreamAsync(string url, PdfOptions? options = null) => throw new NotImplementedException(ErrorMessage); - /// - /// Export method - /// - /// html raw string - /// - /// - public Task PdfDataFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null) => throw new NotImplementedException(ErrorMessage); + public Task PdfDataFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null, PdfOptions? options = null) => throw new NotImplementedException(ErrorMessage); - /// - /// Export method - /// - /// html raw string - /// - /// - public Task PdfStreamFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null) => throw new NotImplementedException(ErrorMessage); + public Task PdfStreamFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null, PdfOptions? options = null) => throw new NotImplementedException(ErrorMessage); } diff --git a/src/BootstrapBlazor/Services/IHtml2Pdf.cs b/src/BootstrapBlazor/Services/IHtml2Pdf.cs index 77693f7c8d4..1bf56d703d8 100644 --- a/src/BootstrapBlazor/Services/IHtml2Pdf.cs +++ b/src/BootstrapBlazor/Services/IHtml2Pdf.cs @@ -14,13 +14,15 @@ public interface IHtml2Pdf /// Export method /// /// url - Task PdfDataAsync(string url); + /// + Task PdfDataAsync(string url, PdfOptions? options = null); /// /// Export method /// /// url - Task PdfStreamAsync(string url); + /// + Task PdfStreamAsync(string url, PdfOptions? options = null); /// /// Export method @@ -28,7 +30,8 @@ public interface IHtml2Pdf /// html raw string /// /// - Task PdfDataFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null); + /// + Task PdfDataFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null, PdfOptions? options = null); /// /// Export method @@ -36,5 +39,6 @@ public interface IHtml2Pdf /// html raw string /// /// - Task PdfStreamFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null); + /// + Task PdfStreamFromHtmlAsync(string html, IEnumerable? links = null, IEnumerable? scripts = null, PdfOptions? options = null); } From 63ad92d9b79335dfdb36d99137c9c80672a791dd Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Mon, 27 Oct 2025 12:42:26 +0800 Subject: [PATCH 05/25] chore: bump version 9.6.6 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index a60e5c4b2e3..0fedabb7802 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.5 + 9.6.6 From cce713e8b9c2ff2c85dc30470d65caebc7d22273 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Wed, 29 Oct 2025 17:34:43 +0800 Subject: [PATCH 06/25] =?UTF-8?q?refactor:=20=E8=B0=83=E6=95=B4=20IconTemp?= =?UTF-8?q?late=20=E4=BC=98=E5=85=88=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Upload/CardUpload.razor | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor/Components/Upload/CardUpload.razor b/src/BootstrapBlazor/Components/Upload/CardUpload.razor index 9a02c28b95f..47f3614fe74 100644 --- a/src/BootstrapBlazor/Components/Upload/CardUpload.razor +++ b/src/BootstrapBlazor/Components/Upload/CardUpload.razor @@ -16,13 +16,13 @@ {
- @if (IsImage(item)) + @if (IconTemplate != null) { - prevUrl + @IconTemplate(item) } - else if (IconTemplate != null) + else if (IsImage(item)) { - @IconTemplate(item) + prevUrl } else { From c8a6c7c87235985d06e242c71dca5d84da425b6c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 30 Oct 2025 11:26:40 +0800 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20ActionButton?= =?UTF-8?q?Template=20=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Upload/CardUpload.razor | 4 ++++ src/BootstrapBlazor/Components/Upload/CardUpload.razor.cs | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/BootstrapBlazor/Components/Upload/CardUpload.razor b/src/BootstrapBlazor/Components/Upload/CardUpload.razor index 47f3614fe74..78003ea28b1 100644 --- a/src/BootstrapBlazor/Components/Upload/CardUpload.razor +++ b/src/BootstrapBlazor/Components/Upload/CardUpload.razor @@ -32,6 +32,10 @@
@item.GetFileName() (@item.Size.ToFileSizeString())
+ @if (ActionButtonTemplate) + { + @ActionButtonTemplate(item) + } @if (ShowZoomButton) {
/// - protected virtual Task InvokeInitAsync() => InvokeVoidAsync("init", Id); + protected virtual Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop); /// /// call JavaScript method From 77da6d8a1c3638010790c5f18cbcd8220ef33fdd Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 9 Dec 2025 15:09:47 +0800 Subject: [PATCH 15/25] chore: bump version 9.6.10 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 1c50be3be26..ecba6924b20 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.9 + 9.6.10 From 924fcb8d5ae232ef29195cd503999baaa343141e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 16 Dec 2025 18:51:03 +0800 Subject: [PATCH 16/25] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20OnBeforeTree?= =?UTF-8?q?ItemClick=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/TreeView/TreeView.razor.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs b/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs index 58a5c28161a..dd90e419123 100644 --- a/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs +++ b/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -152,6 +152,12 @@ public partial class TreeView : IModelEqualityComparer [Parameter] public Func, Task>? OnTreeItemClick { get; set; } + /// + /// 获得/设置 点击节点前回调方法 + /// + [Parameter] + public Func, Task>? OnBeforeTreeItemClick { get; set; } + /// /// Gets or sets the callback method when a tree item is checked. /// @@ -545,6 +551,15 @@ private async Task>> GetChildrenRowAsync(Tree private async Task OnClick(TreeViewItem item) { + if (OnBeforeTreeItemClick != null) + { + var ret = await OnBeforeTreeItemClick(item); + if (ret == false) + { + return; + } + } + _activeItem = item; if (ClickToggleNode && item.CanTriggerClickNode(IsDisabled, CanExpandWhenDisabled)) { From ffde92e1fa47a7cabe4b482a76023eea4b494d37 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Tue, 16 Dec 2025 18:56:47 +0800 Subject: [PATCH 17/25] chore: bump version 9.6.11 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index ecba6924b20..d6446a2f2ad 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.10 + 9.6.11 From 9fb4edae15a7118a87d652ff3f6a398d7f7ea5b2 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Dec 2025 15:52:15 +0800 Subject: [PATCH 18/25] =?UTF-8?q?refactor:=20=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=B5=8C=E5=A5=97=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Drawer/Drawer.razor.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/BootstrapBlazor/Components/Drawer/Drawer.razor.js b/src/BootstrapBlazor/Components/Drawer/Drawer.razor.js index a9c72fb3e40..064197cd6e7 100644 --- a/src/BootstrapBlazor/Components/Drawer/Drawer.razor.js +++ b/src/BootstrapBlazor/Components/Drawer/Drawer.razor.js @@ -9,7 +9,7 @@ const initDrag = el => { let height = 0; let isVertical = false; const drawerBody = el.querySelector('.drawer-body'); - const bar = el.querySelector('.drawer-bar'); + const bar = [...drawerBody.children].find(i => i.classList.contains('drawer-bar')); Drag.drag(bar, e => { isVertical = drawerBody.classList.contains("top") || drawerBody.classList.contains("bottom") @@ -105,7 +105,7 @@ export function execute(id, open) { showDrawer(); } } - + const showDrawer = () => { drawerBody.classList.add('show'); if (drawerBackdrop) { @@ -175,7 +175,8 @@ export function dispose(id) { body.classList.remove('overflow-hidden') } - const bar = el.querySelector('.drawer-bar'); + const drawerBody = el.querySelector('.drawer-body'); + const bar = [...drawerBody.children].find(i => i.classList.contains('drawer-bar')); if (bar) { Drag.dispose(bar) } From ae0b16f43b5827037a1d4a021690b68684ffeb5c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Dec 2025 15:52:29 +0800 Subject: [PATCH 19/25] chore: bump version 9.6.12 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index d6446a2f2ad..227eef21522 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.11 + 9.6.12 From 16782ef8c2f6a15b95f7448fcf1033872875017e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Dec 2025 20:09:07 +0800 Subject: [PATCH 20/25] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/IpAddress/IpAddress.razor.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js b/src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js index e5cc20cbaf3..f97b93777cf 100644 --- a/src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js +++ b/src/BootstrapBlazor/Components/IpAddress/IpAddress.razor.js @@ -86,15 +86,22 @@ export function init(id, invoke) { if (!raw) { return; } - const parts = raw.replace(/[^\d.]/g, '').split('.').filter(p => p.length); + + const ipRegex = /\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/; + const match = raw.match(ipRegex); + const parts = match ? match[0] : null; + if (parts === null) { + return; + } + const cells = el.querySelectorAll(".ipv4-cell"); let pos = 0; const args = []; - parts.forEach(p => { + parts.split('.').forEach(p => { if (pos > 3) { return; } - const num = Math.max(0, Math.min(255, parseInt(p, 10) || 0)); + const num = parseInt(p, 10); args.push(num); cells[pos].value = num.toString(); ip.prevValues[pos] = cells[pos].value; From 740ebf15dc266d769f1c49131929cd8ce13afd8e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Dec 2025 20:12:49 +0800 Subject: [PATCH 21/25] chore: bump version 9.6-13-beta01 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 227eef21522..3c160738a7a 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.12 + 9.6.13-beta01 From 1efed9acbd55679e471c30843d40809d8278143a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 18 Dec 2025 20:21:54 +0800 Subject: [PATCH 22/25] chore: bump version 9.6.13 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 3c160738a7a..4acb490644a 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.13-beta01 + 9.6.13 From 8b0b6371b4840e27069d91fc3af6bf01b5c1e02c Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 19 Dec 2025 14:47:15 +0800 Subject: [PATCH 23/25] feat(ZipArchiveService): add ArchiveDirectoryAsync method # Conflicts: # src/BootstrapBlazor/Services/DefaultZipArchiveService.cs # src/BootstrapBlazor/Services/IZipArchiveService.cs --- .../Services/DefaultZipArchiveService.cs | 87 ++++++++++--------- .../Services/IZipArchiveService.cs | 52 +++++++---- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs index e872ab3eeda..aca5aa5183d 100644 --- a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs +++ b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -13,15 +13,12 @@ class DefaultZipArchiveService : IZipArchiveService /// /// /// - /// 要归档的文件集合 - /// 归档配置 - /// 归档数据流 - public async Task ArchiveAsync(IEnumerable files, ArchiveOptions? options = null) + public async Task ArchiveAsync(IEnumerable entries, ArchiveOptions? options = null) { var stream = new MemoryStream(); options ??= new ArchiveOptions(); options.LeaveOpen = true; - await ArchiveFilesAsync(stream, files, options); + await ArchiveFilesAsync(stream, entries, options); stream.Position = 0; return stream; } @@ -29,31 +26,41 @@ public async Task ArchiveAsync(IEnumerable files, ArchiveOptions /// /// /// - /// 归档文件 - /// 要归档的文件集合 - /// 归档配置 - public async Task ArchiveAsync(string archiveFileName, IEnumerable files, ArchiveOptions? options = null) + public async Task ArchiveAsync(string archiveFile, IEnumerable entries, ArchiveOptions? options = null) { - using var stream = File.OpenWrite(archiveFileName); - await ArchiveFilesAsync(stream, files, options); + using var stream = File.OpenWrite(archiveFile); + await ArchiveFilesAsync(stream, entries, options); } - private static async Task ArchiveFilesAsync(Stream stream, IEnumerable files, ArchiveOptions? options = null) + private static async Task ArchiveFilesAsync(Stream stream, IEnumerable entries, ArchiveOptions? options = null) { options ??= new ArchiveOptions(); using var archive = new ZipArchive(stream, options.Mode, options.LeaveOpen, options.Encoding); - foreach (var f in files) + foreach (var f in entries) { if (options.ReadStreamAsync != null) { - var entry = archive.CreateEntry(Path.GetFileName(f), options.CompressionLevel); - using var entryStream = entry.Open(); - await using var content = await options.ReadStreamAsync(f); + var entry = archive.CreateEntry(f.EntryName, options.CompressionLevel); + await using var content = await options.ReadStreamAsync(f.SourceFileName); + await using var entryStream = entry.Open(); await content.CopyToAsync(entryStream); + entryStream.Close(); + } + else if (Directory.Exists(f.SourceFileName)) + { + var entryName = f.EntryName; + if (!string.IsNullOrEmpty(entryName)) + { + if (!entryName.EndsWith(Path.DirectorySeparatorChar)) + { + entryName = $"{entryName}{Path.DirectorySeparatorChar}"; + } + archive.CreateEntry(entryName, f.CompressionLevel ?? options.CompressionLevel); + } } else { - archive.CreateEntryFromFile(f, Path.GetFileName(f), options.CompressionLevel); + archive.CreateEntryFromFile(f.SourceFileName, f.EntryName, f.CompressionLevel ?? options.CompressionLevel); } } } @@ -61,52 +68,52 @@ private static async Task ArchiveFilesAsync(Stream stream, IEnumerable f /// /// /// - /// 归档文件 - /// 要归档文件夹 - /// - /// - /// - /// - /// - public async Task ArchiveDirectory(string archiveFileName, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null) + public async Task ArchiveDirectoryAsync(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default) { if (Directory.Exists(directoryName)) { - var folder = Path.GetDirectoryName(archiveFileName); + var folder = Path.GetDirectoryName(archiveFile); if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder)) { Directory.CreateDirectory(folder); } - await Task.Run(() => ZipFile.CreateFromDirectory(directoryName, archiveFileName, compressionLevel, includeBaseDirectory, encoding)); +#if NET10_0_OR_GREATER + await ZipFile.CreateFromDirectoryAsync(directoryName, archiveFile, compressionLevel, includeBaseDirectory, encoding, token); +#else + await Task.Run(() => + { + token.ThrowIfCancellationRequested(); + ZipFile.CreateFromDirectory(directoryName, archiveFile, compressionLevel, includeBaseDirectory, encoding); + }, token); +#endif } } /// /// /// - /// 归档文件 - /// 解压缩文件夹 - /// 是否覆盖文件 默认 false 不覆盖 - /// 编码方式 默认 null 内部使用 UTF-8 - /// - public bool ExtractToDirectory(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null) + public async Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default) { if (!Directory.Exists(destinationDirectoryName)) { Directory.CreateDirectory(destinationDirectoryName); } - ZipFile.ExtractToDirectory(archiveFile, destinationDirectoryName, encoding, overwriteFiles); + +#if NET10_0_OR_GREATER + await ZipFile.ExtractToDirectoryAsync(archiveFile, destinationDirectoryName, encoding, overwriteFiles, token); +#else + await Task.Run(() => + { + token.ThrowIfCancellationRequested(); + ZipFile.ExtractToDirectory(archiveFile, destinationDirectoryName, encoding, overwriteFiles); + }, token); +#endif return true; } /// /// /// - /// 归档文件 - /// 解压缩文件 - /// 是否覆盖文件 默认 false 不覆盖 - /// 编码方式 默认 null 内部使用 UTF-8 - /// public ZipArchiveEntry? GetEntry(string archiveFile, string entryFile, bool overwriteFiles = false, Encoding? encoding = null) { using var archive = ZipFile.Open(archiveFile, ZipArchiveMode.Read, encoding); diff --git a/src/BootstrapBlazor/Services/IZipArchiveService.cs b/src/BootstrapBlazor/Services/IZipArchiveService.cs index 2514f9b6c17..1bcdf2f33a6 100644 --- a/src/BootstrapBlazor/Services/IZipArchiveService.cs +++ b/src/BootstrapBlazor/Services/IZipArchiveService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -16,39 +16,39 @@ public interface IZipArchiveService /// /// 将文件归档方法 /// - /// 要归档的文件集合 + /// 要归档的文件集合 /// 归档配置 /// 归档数据流 - Task ArchiveAsync(IEnumerable files, ArchiveOptions? options = null); + Task ArchiveAsync(IEnumerable entries, ArchiveOptions? options = null); /// /// 将文件归档方法 /// - /// 归档文件 - /// 要归档的文件集合 + /// 归档文件 + /// 要归档的文件集合 /// 归档配置 - Task ArchiveAsync(string archiveFileName, IEnumerable files, ArchiveOptions? options = null); + Task ArchiveAsync(string archiveFile, IEnumerable entries, ArchiveOptions? options = null); /// /// 将指定目录归档方法 /// - /// 归档文件 + /// 归档文件 /// 要归档文件夹 - /// - /// - /// - /// - Task ArchiveDirectory(string archiveFileName, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null); + /// 压缩率 + /// 是否包含本目录 默认 false + /// 编码方式 默认 null 内部使用 UTF-8 + /// + Task ArchiveDirectoryAsync(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default); /// - /// 解压缩归档文件到指定文件夹 + /// 解压缩归档文件到指定文件夹异步方法 /// /// 归档文件 /// 解压缩文件夹 /// 是否覆盖文件 默认 false 不覆盖 /// 编码方式 默认 null 内部使用 UTF-8 - /// - bool ExtractToDirectory(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null); + /// + Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default); /// /// 获得归档压缩文件中指定归档文件 @@ -57,6 +57,26 @@ public interface IZipArchiveService /// 解压缩文件 /// 是否覆盖文件 默认 false 不覆盖 /// 编码方式 默认 null 内部使用 UTF-8 - /// ZipArchiveEntry? GetEntry(string archiveFile, string entryFile, bool overwriteFiles = false, Encoding? encoding = null); } + +/// +/// 归档项实体类 +/// +public readonly record struct ArchiveEntry +{ + /// + /// 获得 物理文件 + /// + public string SourceFileName { get; init; } + + /// + /// 获得 归档项 + /// + public string EntryName { get; init; } + + /// + /// 获得 压缩配置 + /// + public CompressionLevel? CompressionLevel { get; init; } +} From fd7a4708e625a1d8bf279523e0ce2a18fef747e1 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 19 Dec 2025 14:47:22 +0800 Subject: [PATCH 24/25] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # test/UnitTest/Services/ZipArchiveServiceTest.cs --- .../Services/ZipArchiveServiceTest.cs | 102 ++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/test/UnitTest/Services/ZipArchiveServiceTest.cs b/test/UnitTest/Services/ZipArchiveServiceTest.cs index b4dd9caae67..0e5bd852e74 100644 --- a/test/UnitTest/Services/ZipArchiveServiceTest.cs +++ b/test/UnitTest/Services/ZipArchiveServiceTest.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using System.IO.Compression; + namespace UnitTest.Services; public class ZipArchiveServiceTest : BootstrapBlazorTestBase @@ -22,10 +24,15 @@ public async Task Archive_Ok() using var fs = File.OpenWrite(f); fs.WriteByte(65); }); - var stream = await archService.ArchiveAsync(files); + var items = files.Select(i => new ArchiveEntry() + { + SourceFileName = i, + EntryName = Path.GetFileName(i) + }); + var stream = await archService.ArchiveAsync(items); Assert.NotNull(stream); - stream = await archService.ArchiveAsync(files, new ArchiveOptions() + stream = await archService.ArchiveAsync(items, new ArchiveOptions() { CompressionLevel = System.IO.Compression.CompressionLevel.Optimal, Encoding = System.Text.Encoding.UTF8, @@ -35,7 +42,7 @@ public async Task Archive_Ok() Assert.NotNull(stream); var archiveFile = Path.Combine(root, "test.zip"); - await archService.ArchiveAsync(archiveFile, files); + await archService.ArchiveAsync(archiveFile, items); Assert.True(File.Exists(archiveFile)); // GetEntry @@ -48,7 +55,15 @@ public async Task Archive_Ok() { Directory.Delete(destFolder, true); } - archService.ExtractToDirectory(archiveFile, destFolder); + await archService.ExtractToDirectoryAsync(archiveFile, destFolder); + Assert.True(Directory.Exists(destFolder)); + + // 删除文件夹 + Directory.Delete(destFolder, true); + Assert.False(Directory.Exists(destFolder)); + + // 异步解压缩单元测试 + await archService.ExtractToDirectoryAsync(archiveFile, destFolder); Assert.True(Directory.Exists(destFolder)); // 打包文件夹单元测试 @@ -62,10 +77,83 @@ public async Task Archive_Ok() { File.Delete(destFile); } - await archService.ArchiveDirectory(destFile, destFolder, includeBaseDirectory: true); + await archService.ArchiveDirectoryAsync(destFile, destFolder, includeBaseDirectory: true); Assert.True(File.Exists(destFile)); File.Delete(destFile); - await Assert.ThrowsAsync(() => archService.ArchiveDirectory(null!, destFolder, includeBaseDirectory: true)); + await Assert.ThrowsAsync(() => archService.ArchiveDirectoryAsync(null!, destFolder, includeBaseDirectory: true)); + } + + [Fact] + public async Task ZipArchive_Ok() + { + var fileName = Path.Combine(AppContext.BaseDirectory, "test", "3.zip"); + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + using var fs = File.OpenWrite(fileName); + using var zip = new ZipArchive(fs, ZipArchiveMode.Create); + + var item = Path.Combine(AppContext.BaseDirectory, "test", "1.txt"); + zip.CreateEntry("text/"); + await zip.CreateEntryFromFileAsync(item, "text/1.txt"); + } + + [Fact] + public async Task ArchiveAsync_Ok() + { + var fileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test.zip"); + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + var root = AppContext.BaseDirectory; + var files = new string[] + { + Path.Combine(root, "archive_test", "test1", "1.txt"), + Path.Combine(root, "archive_test", "test2", "2.txt") + }; + files.ToList().ForEach(f => + { + var folder = Path.GetDirectoryName(f); + if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + using var fs = File.OpenWrite(f); + fs.WriteByte(65); + }); + + var archService = Context.Services.GetRequiredService(); + await archService.ArchiveAsync(fileName, new List() + { + new ArchiveEntry() + { + SourceFileName = files[0], + EntryName = "test1/test.log" + }, + new ArchiveEntry() + { + SourceFileName = files[1], + EntryName = "test2/test.log", + CompressionLevel = CompressionLevel.Optimal + }, + new ArchiveEntry() + { + SourceFileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test1"), + EntryName = "test1", + }, + new ArchiveEntry() + { + SourceFileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test1"), + EntryName = "test2", + CompressionLevel = CompressionLevel.Optimal + } + }); + + Assert.True(File.Exists(fileName)); } } From 69c80c27d7def5a246315cfcb3b14a5ba835702a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Fri, 19 Dec 2025 16:19:03 +0800 Subject: [PATCH 25/25] chore: bump version 9.6.14 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 4acb490644a..3e8202eb2cb 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 9.6.13 + 9.6.14