You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 07:57:03