Grails框架下如何实现GSP页面表格的多列排序功能?
问题根源
- 表格采用「用户主行 + 多组关联游戏行/展开行」的嵌套结构,原排序逻辑将所有行视为独立个体,会打散用户和对应游戏数据的绑定关系
- 游戏行的排序对应列多为空值,和用户主行的列值比较时会触发逻辑异常,就是你截图中报错的原因
- 注册日期、验证日期直接用格式化后的字符串比较,会出现非预期的排序结果(比如10月排在2月前面)
可行解决方案
第一步:修改GSP代码,给行加分组标记
给每个用户的所有关联行绑定相同的data-user-id属性,同时给行加类型标识,可排序列存储原始值避免格式化字符串的比较误差:
<table style="width: 100%" id="sorttables"> <thead> <tr> <th onclick="sortTable(0)">User Id</th> <th onclick="sortTable(1)">First Name</th> <th onclick="sortTable(2)">Last Name</th> <th>Email</th> <th>Phone</th> <th onclick="sortTable(3)">Registration Date</th> <th onclick="sortTable(4)">Validation Date</th> </tr> </thead> <tbody id="test"> <g:each in="${userStats}" var="userStat"> <!-- 用户主行加标识 --> <tr style="background-color: #efefef; border: solid white;" data-user-id="${userStat.id}" data-type="user" > <td data-value="${userStat.id}">${userStat.id}</td> <td data-value="${userStat.firstName}"><b>${userStat.firstName}</b></td> <td data-value="${userStat.lastName}"><b>${userStat.lastName}</b></td> <td><b>${userStat.email}</b></td> <td><b>${userStat.phone}</b></td> <td data-value="${userStat.registrationDate.getTime()}"> <g:formatDate date="${userStat.registrationDate}" type="date" style="MEDIUM"/> </td> <td data-value="${userStat.validationDate?.getTime() ?: 0}"> <g:formatDate date="${userStat.validationDate}" type="date" style="MEDIUM"/> </td> </tr> <g:each in="${userStat.games}" var="game" > <!-- 游戏行绑定相同用户ID --> <tr onclick="unfoldStats(${game.id}, ${userStat.id},'${params.startDate}', '${params.endDate}')" data-user-id="${userStat.id}" data-type="game" > <td></td> <td colspan="5" style="cursor: pointer"> Game: <b>${game.name}</b> (opened: ${game.timeOpened} times. Current High scores: ${game.highestScore}/${game.maxScore}, last played: <g:formatDate date="${game.latestPlayed}" type="date" style="MEDIUM"/>) <g:if test="${game.numberGameEvents > 0}"><i class="fas fa-arrow-circle-down"></i></g:if> </td> </tr> <tr data-user-id="${userStat.id}" data-type="game" > <td></td> <td colspan="5" id="user-game-${game.id}-${userStat.id}"></td> </tr> </g:each> </g:each> </tbody> </table>
第二步:重写JS排序逻辑,按用户分组排序
排序时以用户为单位,整组移动所有关联行,避免数据拆分:
function sortTable(n) { const table = document.getElementById("sorttables"); const tbody = table.querySelector("tbody"); // 按用户ID分组,存储每个用户的所有关联行 const userGroups = new Map(); // 遍历所有行,分组 Array.from(tbody.rows).forEach(row => { const userId = row.dataset.userId; if (!userGroups.has(userId)) { userGroups.set(userId, []); } userGroups.get(userId).push(row); }); // 取出所有用户组,只拿每组的用户主行做比较 const sortedGroups = Array.from(userGroups.values()).sort((groupA, groupB) => { const userRowA = groupA.find(row => row.dataset.type === "user"); const userRowB = groupB.find(row => row.dataset.type === "user"); // 取存的原始值比较 let valA = userRowA.cells[n].dataset.value; let valB = userRowB.cells[n].dataset.value; // 数字类型转数值比较 if (!isNaN(Number(valA)) && !isNaN(Number(valB))) { valA = Number(valA); valB = Number(valB); } else { // 字符串转小写比较 valA = valA.toLowerCase(); valB = valB.toLowerCase(); } // 切换升降序,默认第一次升序,第二次降序 if (!this.currentDir || this.currentDir === "desc") { this.currentDir = "asc"; return valA > valB ? 1 : -1; } else { this.currentDir = "desc"; return valA < valB ? 1 : -1; } }); // 清空tbody,按排序后的顺序插入所有行 tbody.innerHTML = ""; sortedGroups.forEach(group => { group.forEach(row => tbody.appendChild(row)); }); }
注意事项
如果你的验证日期允许为空,上面代码里已经用?: 0给空值设置了默认排序优先级,你可以根据需求调整默认值。
内容的提问来源于stack exchange,提问作者Sparsh Gahlot
相关产品推荐
相关产品推荐

