Kendo Grid页面返回后定位上次选中元素的实现方案咨询
这个需求完全可行,我来给你梳理下实现思路和具体代码方案
首先核心问题是:滚动加载模式下,选中的记录不在当前加载的数据集里,所以没法直接选中。解决思路就是先定位到目标记录所在的页码,让Grid加载那一页的数据,再选中目标行,同时还要恢复之前的排序状态。
下面是分步实现方案:
一、先做好状态存储:把选中ID、排序、过滤参数存到Cookie里
用户离开页面时,我们需要把当前的关键状态(选中的用户ID、排序规则、过滤条件)保存到Cookie,这样返回页面时能读取恢复。
1. 前端辅助函数:Cookie读写
// 保存数据到Cookie function setCookie(name, value, days = 7) { let expires = ""; if (days) { const date = new Date(); date.setTime(date.getTime() + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = `${name}=${value || ""}${expires}; path=/`; } // 从Cookie读取数据 function getCookie(name) { const nameEQ = `${name}=`; const ca = document.cookie.split(';'); for(let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') c = c.substring(1); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length); } return null; }
2. 给Grid绑定事件,自动保存状态
修改你的Grid视图代码,添加Change(选中行)、Sort(排序变化)事件:
@(Html.Kendo().Grid<RegistryUserDto>() .Name("grid") .Columns(columns => { columns.Bound(o => o.Name).Title(UserResources.NameLabelText).Width(180); columns.Bound(o => o.CompanyName).Title(UserResources.CompanyLabelText).Width(187); columns.Bound(o => o.RoleNames).Title(UserResources.TypeLabelText).Width(100); columns.Bound(o => o.Id).Hidden(); }) .Sortable() .Selectable(x => x.Mode(GridSelectionMode.Single)) .Scrollable() .Events(events => { events.Change("onGridRowSelect"); events.Sort("onGridSortChange"); events.DataBound("onGridDataLoaded"); }) .DataSource(dataSource => dataSource .Ajax() .PageSize(50) // 改成你需求的每次加载50条 .Read(read => read.Action("GetUsersList", "Users").Data("GetFilterParameters")) ) )
然后写事件处理函数,保存状态到Cookie:
// 选中行时保存用户ID function onGridRowSelect(e) { const selectedRow = this.select(); const dataItem = this.dataItem(selectedRow); if (dataItem) { setCookie("SelectedRegistryUserId", dataItem.Id); } } // 排序变化时保存排序规则 function onGridSortChange(e) { const sortConfig = e.sender.dataSource.sort(); if (sortConfig?.length) { setCookie("RegistryGridSort", JSON.stringify(sortConfig)); } } // 额外:如果有过滤功能,也要保存过滤参数(比如在GetFilterParameters函数里添加保存逻辑) function GetFilterParameters() { const params = { // 你的过滤参数逻辑 Name: $("#NameFilter").val(), CompanyId: $("#CompanyFilter").val(), RoleId: $("#RoleFilter").val() }; // 保存过滤参数到Cookie setCookie("RegistryGridFilters", JSON.stringify(params)); return params; }
二、页面加载时恢复状态:定位到目标页并选中行
页面初始化时,我们读取Cookie里的状态,先恢复排序和过滤,再找到目标用户所在的页码,让Grid加载对应数据后选中行。
1. 页面初始化逻辑
$(document).ready(function() { const grid = $("#grid").data("kendoGrid"); const savedUserId = getCookie("SelectedRegistryUserId"); const savedSort = getCookie("RegistryGridSort"); const savedFilters = getCookie("RegistryGridFilters"); // 1. 恢复过滤参数 if (savedFilters) { const filterParams = JSON.parse(savedFilters); // 把参数回填到过滤输入框 $("#NameFilter").val(filterParams.Name); $("#CompanyFilter").val(filterParams.CompanyId); $("#RoleFilter").val(filterParams.RoleId); } // 2. 恢复排序规则 if (savedSort) { grid.dataSource.sort(JSON.parse(savedSort)); } // 3. 如果有保存的选中用户ID,先找它所在的页码 if (savedUserId) { $.ajax({ url: "@Url.Action("GetUserPageNumber", "Users")", type: "GET", data: { userId: savedUserId, parameters: GetFilterParameters() // 带上当前过滤参数,保证页码计算准确 }, success: function(pageNumber) { if (pageNumber > 0) { // 跳转到目标页码,加载对应数据 grid.dataSource.page(pageNumber); // 把用户ID存到全局变量,方便数据加载完成后选中 window.targetUserId = savedUserId; } } }); } }); // 数据加载完成后,选中目标行 function onGridDataLoaded(e) { const grid = e.sender; const targetUserId = window.targetUserId; if (!targetUserId) return; // 找到目标行 const targetRow = grid.tbody.find("tr[data-uid]").filter(function() { const rowData = grid.dataItem(this); return rowData.Id === targetUserId; }); if (targetRow.length) { // 选中行并滚动到可见位置 grid.select(targetRow); grid.content.scrollTop(targetRow.offset().top - grid.content.offset().top); // 清空全局变量,避免重复处理 delete window.targetUserId; } }
2. 后台添加计算页码的方法
需要在UsersController里新增一个方法,根据用户ID和过滤参数,计算出该用户在分页列表中的页码:
public ActionResult GetUserPageNumber(string userId, string parameters) { var requestViewModel = new GetUsersRequestViewModel(); if (!string.IsNullOrWhiteSpace(parameters)) { requestViewModel = JsonConvert.DeserializeObject<GetUsersRequestViewModel>(parameters); } var requestDto = new GetRegistryUsersRequestDto() { Name = requestViewModel.Name, CompanyId = requestViewModel.CompanyId, RoleId = requestViewModel.RoleId }; // 注意:如果数据量极大(百万级),不要直接ToList(),改用数据库层面计算位置(比如ROW_NUMBER()) var filteredUsers = _usersDomainService.GetRegistryUsers(requestDto).ToList(); var userIndex = filteredUsers.FindIndex(u => u.Id == userId); if (userIndex == -1) { // 用户不存在或不符合过滤条件,返回-1 return Json(-1, JsonRequestBehavior.AllowGet); } // 计算页码(PageSize=50,页码从1开始) var pageNumber = (userIndex / 50) + 1; return Json(pageNumber, JsonRequestBehavior.AllowGet); }
三、性能优化提示(针对大数据量场景)
如果你的用户数据量非常大(比如超过10万条),直接ToList()加载所有过滤后的用户会导致性能问题,这时候需要修改后台的页码计算逻辑:
- 在数据库查询时,使用
ROW_NUMBER()窗口函数,根据当前的排序规则给用户列表编号,直接查询目标用户的行号,再计算页码。 - 示例SQL逻辑大致如下:
SELECT ROW_NUMBER() OVER(ORDER BY [排序字段] [排序方向]) AS RowNum, Id FROM RegistryUsers WHERE [过滤条件] HAVING Id = @TargetUserId
这样就能避免加载所有数据,大幅提升性能。
内容的提问来源于stack exchange,提问作者Oleksa Kachmarskyi
相关产品推荐
相关产品推荐

