Blazor .NET Core 5 可拖拽仪表板瓦片的本地存储用户偏好持久化实现
项目现状与待解决问题
- 开发环境:
.NET Core 5+Visual Studio 2019 - 已实现功能:仪表板应用可从数据库加载对应应用模块并展示给用户,界面效果参考:

- 已完成交互:通过jQuery实现界面瓦片(tile)的拖拽、调整尺寸功能
- 核心待解决问题:拖动瓦片后如何持久化保存布局状态,确保用户次日访问应用时仍能保留自己的自定义布局偏好
- 参考瓶颈:曾尝试参考Telerik官方演示实现状态持久化,但学生身份无法承担其商用授权费用,需要免费可替代的实现方案;目前对参考代码中的状态模型设计、状态恢复方法(如
TileLayoutInstance.SetState(storedState))的底层实现逻辑完全不熟悉,无法直接复用商用组件的代码逻辑。
参考的本地存储服务实现
using Microsoft.JSInterop; using System.Text.Json; using System.Threading.Tasks; namespace TelerikBlazorDemos.Services { public class LocalStorage { protected IJSRuntime JSRuntimeInstance { get; set; } public LocalStorage(IJSRuntime jsRuntime) { JSRuntimeInstance = jsRuntime; } public ValueTask SetItem(string key, object data) { return JSRuntimeInstance.InvokeVoidAsync( "localStorage.setItem", new object[] { key, JsonSerializer.Serialize(data) }); } public async Task<T> GetItem<T>(string key) { var data = await JSRuntimeInstance.InvokeAsync<string>("localStorage.getItem", key); if (!string.IsNullOrEmpty(data)) { return JsonSerializer.Deserialize<T>(data); } return default; } public ValueTask RemoveItem(string key) { return JSRuntimeInstance.InvokeVoidAsync("localStorage.removeItem", key); } } }
参考的Razor页面实现
@page "/tilelayout/persist-state" @inject LocalStorage LocalStorage @inject IJSRuntime JsInterop @using TelerikBlazorDemos.Shared.DemoConfigurator <DemoConfigurator> <DemoConfiguratorColumn> <TelerikButton OnClick="@SaveState" Icon="save" Class="mr-xs">Save State</TelerikButton> <TelerikButton OnClick="@ReloadPage" Icon="reload" Class="mr-xs">Reload the page</TelerikButton> <TelerikButton OnClick="@LoadState" Icon="download" Class="mr-xs">Load last State</TelerikButton> <TelerikButton OnClick="@SetExplicitState" Icon="gear" Class="mr-xs">Configure State</TelerikButton> </DemoConfiguratorColumn> </DemoConfigurator> <div class="demo-alert demo-alert-info" role="alert"> <strong>调整瓦片布局</strong>(调整部分瓦片的尺寸、顺序,记住调整后的顺序),点击<strong>保存</strong>将TileLayout的状态持久化,再点击<strong>重新加载</strong>页面即可看到状态保留效果。可再次调整布局后点击<strong>加载上次状态</strong>,即可恢复之前保存的布局配置。也可通过「配置状态」按钮自行编写代码将TileLayout设置为指定布局。本演示会一直记住你上次的布局,直到你清除浏览器存储或点击「配置状态」按钮重置为初始布局。 </div> <div style="display: inline-block;"> <TelerikTileLayout @ref="@TileLayoutInstance" Columns="3" ColumnWidth="285px" RowHeight="285px" Resizable="true" Reorderable="true"> <TileLayoutItems> <TileLayoutItem HeaderText="San Francisco"> <Content> <img class="k-card-image" draggable="false" src="images/cards/sanfran.jpg" /> </Content> </TileLayoutItem> <TileLayoutItem HeaderText="South Africa"> <Content> <img class="k-card-image" draggable="false" src="images/cards/south-africa.jpg" /> </Content> </TileLayoutItem> <TileLayoutItem HeaderText="Sofia" RowSpan="2"> <Content> <img class="k-card-image" draggable="false" src="images/cards/sofia.jpg" /> <div class="text-center"> <div class="k-card-body"> <p>Sofia is the largest city and capital of Bulgaria. Sofia City Province has an area of 1344 km<sup>2</sup> while the surrounding and much bigger Sofia Province is 7,059 km<sup>2</sup>. The city is situated in the western part of the country at the northern foot of the Vitosha mountain, in the Sofia Valley that is surrounded by the Balkan mountains to the north. The valley has an average altitude of 550 metres (1,800 ft). Unlike most European capitals, Sofia does not straddle any large river, but is surrounded by comparatively high mountains on all sides.</p> <br /> <em>* The data used in this demo is taken from <a href="https://wikipedia.com" target="_blank"><em>wikipedia.com</em></a></em> </div> </div> </Content> </TileLayoutItem> <TileLayoutItem HeaderText="Rome" ColSpan="2" RowSpan="2"> <Content> <img class="k-card-image image-center" draggable="false" src="images/cards/rome.jpg" /> <div class="text-center"> <div class="k-card-body"> <p> </p> <p> <em>* The data used in this demo is taken from <a href="https://wikipedia.com" target="_blank"><em>wikipedia.com</em></a></em> </p> </div> </div> </Content> </TileLayoutItem> <TileLayoutItem HeaderText="Barcelona"> <Content> <img class="k-card-image" draggable="false" src="images/cards/barcelona.jpg" /> </Content> </TileLayoutItem> </TileLayoutItems> </TelerikTileLayout> </div> @code { TelerikTileLayout TileLayoutInstance { get; set; } TileLayoutState SavedState { get; set; } string stateStorageKey = "TelerikBlazorTileLayoutStateDemoKey"; async Task SaveState() { var state = TileLayoutInstance.GetState(); await LocalStorage.SetItem(stateStorageKey, state); } async Task LoadState() { TileLayoutState storedState = await LocalStorage.GetItem<TileLayoutState>(stateStorageKey); if (storedState != null) { TileLayoutInstance.SetState(storedState); } } void ReloadPage() { JsInterop.InvokeVoidAsync("window.location.reload"); } async void SetExplicitState() { TileLayoutState desiredState = GetDefaultDemoState(); TileLayoutInstance.SetState(desiredState); await SaveState(); } protected override async Task OnAfterRenderAsync(bool firstRender) { var state = await LocalStorage.GetItem<TileLayoutState>(stateStorageKey); if (state != null && TileLayoutInstance != null) { TileLayoutInstance.SetState(state); } } TileLayoutState GetDefaultDemoState() { TileLayoutState defaultDemoState = new TileLayoutState() { ItemStates = new List<TileLayoutItemState>() { new TileLayoutItemState { Order = 1, ColSpan = 1, RowSpan = 1 }, new TileLayoutItemState { Order = 2, ColSpan = 1, RowSpan = 1 }, new TileLayoutItemState { Order = 3, ColSpan = 1, RowSpan = 2 }, new TileLayoutItemState { Order = 4, ColSpan = 2, RowSpan = 2 }, new TileLayoutItemState { Order = 5, ColSpan = 1, RowSpan = 1 }, } }; return defaultDemoState; } } <style> .k-card-image { width: 285px; height: 189px; } .k-card-body { overflow: auto; } .image-center { display: block; margin: auto; } </style>
内容的提问来源于stack exchange,提问作者sinfella
相关产品推荐
相关产品推荐

