From 72ffffabc2cd18f87264e3aec1fd45ec1b66b752 Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 16:19:40 +0800 Subject: [PATCH 1/5] =?UTF-8?q?Table=E7=BB=84=E4=BB=B6=E8=83=BD=E5=90=A6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=A8=E6=80=81=E8=AE=BE=E7=BD=AE=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=88=97=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 可以动态设置固定列,同时固定列支持表格持久化 --- .../Components/Table/Table.razor.cs | 26 +++++++++- .../Components/Table/Table.razor.js | 20 ++++++-- .../Components/Table/TableColumnState.cs | 6 +++ test/UnitTest/Components/TableTest.cs | 49 +++++++++++++++++++ 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 8b71d314d81..328f6ee3b35 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1435,6 +1435,7 @@ private void ResetTableColumns() { var item = _tableColumnStates[index]; var col = columnMap[item.Name]; + col.Fixed = !string.IsNullOrEmpty(item.Name) && stateMap.TryGetValue(item.Name, out var state) ? stateMap[item.Name].Fixed : false; if (item.Visible) { // 增加到可见列缓存集合 @@ -1449,7 +1450,8 @@ private void ResetTableColumns() DisplayName = col.GetDisplayName(), Name = col.GetFieldName(), Width = col.Fixed && !col.Width.HasValue ? DefaultFixedColumnWidth : col.Width, - Visible = col.GetVisible(_screenSize) + Visible = col.GetVisible(_screenSize), + Fixed = col.Fixed }; private async Task OnTableRenderAsync(bool firstRender) @@ -1940,6 +1942,28 @@ public async Task FitAllColumnWidth() await InvokeVoidAsync("fitAllColumnWidth", Id); } + /// + /// 更新表格列客户端状态方法 + /// Update Table Column Client Status Method + /// + /// + public async Task UpdateTableColumnClientStatus() + { + if (Columns.Count != 0) + { + // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中 + foreach (var item in Columns) + { + _tableColumnStates.Find(x => x.Name == item.GetFieldName())!.Fixed = item.Fixed; + } + StateHasChanged(); + } + // 如果启用了 ClientTableName 则更新浏览器持久化列状态 + await InvokeVoidAsync("updateColumnStates", Id); + + return _tableColumnStateCache; + } + /// /// 清除表格列客户端状态实例方法 /// clear table column client status instance method diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index 94804c8322e..48f02cfef78 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -586,7 +586,7 @@ const autoFitColumnWidth = async (table, col) => { const index = indexOfCol(col); let rows = null; let maxWidth = getColumnMaxCellWidth(table, index); - + if (table.options.fitColumnWidthIncludeHeader) { const th = getColumnHeader(col); maxWidth = Math.max(maxWidth, getCellWidth(th)); @@ -936,6 +936,18 @@ const removeColumnWidthState = tableName => { localStorage.removeItem(columnWidthKey); } +export function updateColumnStates(id) { + const el = document.getElementById(id) + if (el === null) { + return + } + let table = Data.get(id) + table.el = el; + Data.set(id, table) + const state = getColumnStateObject(table); + saveColumnStateToLocalstorage(table, state); + +} export function clearColumnStates(tableName) { const columnStateKey = `bb-table-${tableName}`; localStorage.removeItem(columnStateKey); @@ -976,7 +988,8 @@ const getColumnStateObject = table => { return { name: col.name, width: getColumnWidth(col, table.columns), - visible: col.visible + visible: col.visible, + fixed: col.fixed } }), table: getTableWidth(table.tables[0]) @@ -988,7 +1001,8 @@ const getColumnStateObject = table => { return { name: getColumnName(col), width: getResizableColumnWidth(col), - visible: true + visible: true, + fixed: getColumnHeader(col).classList.contains('fixed') } }), table: getTableWidth(table.tables[0]) diff --git a/src/BootstrapBlazor/Components/Table/TableColumnState.cs b/src/BootstrapBlazor/Components/Table/TableColumnState.cs index 1aad8e442d8..3a2e181395a 100644 --- a/src/BootstrapBlazor/Components/Table/TableColumnState.cs +++ b/src/BootstrapBlazor/Components/Table/TableColumnState.cs @@ -38,4 +38,10 @@ public class TableColumnState /// Gets or sets column width /// public int? Width { get; set; } + + /// + /// 获得/设置 列固定 + /// Gets or sets column fixed + /// + public bool Fixed { get; set; } } diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 3f48b29f279..41329364b4e 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9018,6 +9018,55 @@ public async Task OnTableColumnClientStatusChanged_ResizeColumn_Ok() Assert.NotNull(clientState); } + [Fact] + public async Task UpdateTableColumnClientStatus_Ok() + { + var state = new TableColumnClientStatus(); + state.TableWidth = 220; + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Width = 100, Fixed = true }); + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Address), Visible = true, Width = 120, Fixed = false }); + + Context.JSInterop.Setup("getColumnStates", "test_update").SetResult(state); + var invoker = Context.JSInterop.SetupVoid("clearColumnStates", "test_update"); + invoker.SetVoidResult(); + + var localizer = Context.Services.GetRequiredService>(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.ClientTableName, "test_update"); + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.AllowResizing, true); + pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer)); + pb.Add(a => a.TableColumns, foo => builder => + { + builder.OpenComponent>(0); + builder.AddAttribute(1, "Field", "Name"); + builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string))); + builder.AddAttribute(3, "Width", 80); + builder.CloseComponent(); + + builder.OpenComponent>(0); + builder.AddAttribute(3, "Field", "Address"); + builder.AddAttribute(4, "FieldExpression", Utility.GenerateValueExpression(foo, "Address", typeof(string))); + builder.CloseComponent(); + }); + }); + }); + + // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 + var table = cut.FindComponent>(); + var colGroup = table.Find("colgroup"); + Assert.Contains("style=\"width: 100px;\"", colGroup.ToMarkup()); + Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); + + + var status = await cut.InvokeAsync(() => table.Instance.UpdateTableColumnClientStatus()); + Assert.Equal(true, status.Columns[0].Fixed); + Assert.Equal(state.Columns.Count, status.Columns.Count); + } + [Fact] public async Task ClearTableColumnClientStatus_Ok() { From 74435e64a1451bd800b3a94dc9e47d15d209a49a Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 16:50:03 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=B8=85=E9=99=A4=E7=A9=BA=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=E5=88=97=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E4=B8=BA=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor:清除空行,增加缓存列可能为判断 --- src/BootstrapBlazor/Components/Table/Table.razor.cs | 6 +++++- src/BootstrapBlazor/Components/Table/Table.razor.js | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 328f6ee3b35..4f7045596f3 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1954,7 +1954,11 @@ public async Task UpdateTableColumnClientStatus() // 用户在外面变更了列状态后,为避免用户变更状态丢失,须将变更后的状态同步到缓存中 foreach (var item in Columns) { - _tableColumnStates.Find(x => x.Name == item.GetFieldName())!.Fixed = item.Fixed; + var columnState = _tableColumnStates.Find(x => x.Name == item.GetFieldName()); + if (columnState != null) + { + columnState.Fixed = item.Fixed; + } } StateHasChanged(); } diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.js b/src/BootstrapBlazor/Components/Table/Table.razor.js index 48f02cfef78..0de8d1c639b 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.js +++ b/src/BootstrapBlazor/Components/Table/Table.razor.js @@ -946,7 +946,6 @@ export function updateColumnStates(id) { Data.set(id, table) const state = getColumnStateObject(table); saveColumnStateToLocalstorage(table, state); - } export function clearColumnStates(tableName) { const columnStateKey = `bb-table-${tableName}`; From d2b777569f73370f2b43489ee1ea52cca450b05a Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 20:41:34 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=E8=A1=A5=E5=85=85=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=9B=BA=E5=AE=9A=E5=88=97=E6=97=B6=EF=BC=8C=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=88=97=E5=AE=BD=E7=BB=99=E9=BB=98=E8=AE=A4=E5=88=97?= =?UTF-8?q?=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 补充动态固定列时,没有列宽给默认列宽,以防止无列宽时固定后,横向滑动出现错位 --- src/BootstrapBlazor/Components/Table/Table.razor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 4f7045596f3..95695da3ec0 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1958,6 +1958,7 @@ public async Task UpdateTableColumnClientStatus() if (columnState != null) { columnState.Fixed = item.Fixed; + columnState.Width = item.Width.HasValue ? item.Width : (item.Fixed ? DefaultFixedColumnWidth : null); } } StateHasChanged(); From 02a3face8abcb188b16342b0f57cd35a2938941b Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 23:16:50 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 调整指定固定列时,列宽必须有值 test:补充单元测试 --- .../Components/Table/Table.razor.cs | 2 +- test/UnitTest/Components/TableTest.cs | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index 95695da3ec0..65cce099d97 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1958,7 +1958,7 @@ public async Task UpdateTableColumnClientStatus() if (columnState != null) { columnState.Fixed = item.Fixed; - columnState.Width = item.Width.HasValue ? item.Width : (item.Fixed ? DefaultFixedColumnWidth : null); + columnState.Width = item.Fixed && !item.Width.HasValue ? DefaultFixedColumnWidth : item.Width; } } StateHasChanged(); diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 41329364b4e..e610b54aae2 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9023,11 +9023,11 @@ public async Task UpdateTableColumnClientStatus_Ok() { var state = new TableColumnClientStatus(); state.TableWidth = 220; - state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Width = 100, Fixed = true }); + state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Name), Visible = true, Fixed = true }); state.Columns.Add(new TableColumnState() { Name = nameof(Foo.Address), Visible = true, Width = 120, Fixed = false }); Context.JSInterop.Setup("getColumnStates", "test_update").SetResult(state); - var invoker = Context.JSInterop.SetupVoid("clearColumnStates", "test_update"); + var invoker = Context.JSInterop.SetupVoid("updateColumnStates", "test_update"); invoker.SetVoidResult(); var localizer = Context.Services.GetRequiredService>(); @@ -9044,7 +9044,7 @@ public async Task UpdateTableColumnClientStatus_Ok() builder.OpenComponent>(0); builder.AddAttribute(1, "Field", "Name"); builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string))); - builder.AddAttribute(3, "Width", 80); + builder.AddAttribute(3, "Fixed", true); builder.CloseComponent(); builder.OpenComponent>(0); @@ -9058,13 +9058,23 @@ public async Task UpdateTableColumnClientStatus_Ok() // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 var table = cut.FindComponent>(); var colGroup = table.Find("colgroup"); - Assert.Contains("style=\"width: 100px;\"", colGroup.ToMarkup()); Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); - var status = await cut.InvokeAsync(() => table.Instance.UpdateTableColumnClientStatus()); Assert.Equal(true, status.Columns[0].Fixed); Assert.Equal(state.Columns.Count, status.Columns.Count); + + table = cut.FindComponent>(); + var columns = cut.FindAll("th"); + colGroup = table.Find("colgroup"); + Assert.Contains("style=\"width: 200px;\"", colGroup.ToMarkup()); + if (columns[0].ClassName.Contains("fixed")) + { + var fixedWidth = cut.FindAll("col")[0].OuterHtml.Contains("width: 200px"); + Assert.Equal("fixedWidth:True", $"fixedWidth:{fixedWidth}"); + } + + } [Fact] From 33988986414bc92fba998f00fd5e4ee679427e6b Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Fri, 12 Jun 2026 23:20:20 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test:清除多余空行 --- test/UnitTest/Components/TableTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index e610b54aae2..e88e00e0eaf 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -9055,7 +9055,6 @@ public async Task UpdateTableColumnClientStatus_Ok() }); }); - // 由于启用了客户端持久化 Name 列宽使用 100 而非 80 var table = cut.FindComponent>(); var colGroup = table.Find("colgroup"); Assert.Contains("style=\"width: 120px;\"", colGroup.ToMarkup()); @@ -9073,8 +9072,6 @@ public async Task UpdateTableColumnClientStatus_Ok() var fixedWidth = cut.FindAll("col")[0].OuterHtml.Contains("width: 200px"); Assert.Equal("fixedWidth:True", $"fixedWidth:{fixedWidth}"); } - - } [Fact]