资讯动态

Layui表格星级评分显示与排序:从templet渲染到前后端排序

发布时间:2026/10/14 17:18:55 来源:尧图企业网站定制
做后台管理系统这些年跟表格较劲的时间不少。最近手头一个课程管理模块又遇到了那个经典需求列表里要用星星展示课程评分同时表头能点击按评分排序。说白了就是layui的table表格里把评分字段渲染成rate星级评分还要带着sort排序一起工作。这个需求看着不算复杂代码量也不大但实际落地的时候坑不少——最典型的就是templet模板把评分渲染成了星星HTML排序时却直接对着那串HTML字符串比较结果完全对不上。这篇文章我把完整的实现思路拆开从列配置、templet渲染、纯前端排序到后端联调排序都走一遍顺便把我踩过的问题一起列出来给准备做同类功能的朋友一个能直接参考的版本。1. 需求场景与整体思路拆解1.1 这类需求最常出现在哪需要给数据做评分的后台模块十有八九会遇到这个功能。比如课程平台里每门课的学员评价星级、商品管理后台的买家满意度、服务商家的口碑分管理员进了列表页第一反应就是看哪些课程或商品评分高、口碑好这时候表格里能展示星星表头还能按评分升降序排列基本属于标配。另外一个容易被忽略的场景是运营侧。管理员不光是查看还要拿着评分排序结果去做奖惩、做活动邀约、做内容推荐。所以这不只是前端“显示一下”就完了排序结果最好能跟后端数据联动保证在分页情况下排的是全量数据而不是某一页。我最早接到类似需求是在一个内部培训系统的课程管理页当时我不假思索就在前端做了个纯排序结果翻到第二页发现排序乱了原因是表格开了分页layui的前端排序默认只排当前页数据。所以后来我做这个功能第一步永远是问自己这批数据是一次性拉全还是接口分页返回1.2 先确定前端排序还是后端排序这个选择直接决定代码怎么写。前端排序数据一次性加载到表格里没有配置url直接在列上写sort: true点击表头layui内部自动排。优点就是简单、不增加请求。缺点是只能处理当前已加载的数据。如果后台列表不分页数据量也就几十上百条用这个完全够。后端排序表格接接口数据配置了url分页开启。点击表头时需要向服务端发请求由后端按排序字段重新查询返回。这样排的是全量数据翻页后状态依然正确。缺点是要多写一点代码监听sort事件、reload表格、传排序参数后端还要接收参数。我的处理惯例是只要表格配置了url且page为true无脑走后端排序。哪怕当前数据量不大后续量上来不用返工。如果你的列表是本地一次性渲染的静态数据那就走前端排序。1.3 为什么用layui的rate星星而不是自定义图案评分展示的视觉方案其实不少可以画进度条、写数字、用自定义图标。但星星是用户最容易理解的形式五星制评分已经是几乎所有电商平台、课程平台培养出来的用户心智。layui生态里刚好有rate组件它的星星用的是字体图标跟表格里其他图标的风格一致不需要我再额外引入图片或SVG。需要注意rate在页面里是一个完整的动态组件默认需要初始化实例。但表格里每一行的星星如果都要初始化组件成本高还容易出问题。实际中我通常的做法是不是在templet里实例化rate组件而是直接把星标的HTML结构拼出来。因为表格里大多数场景只需要展示不需要用户点击星星打分拼HTML的方式简单可控也不牵扯表格重载导致的组件重复初始化问题。2. 核心配置解析sort跟templet的联动关系2.1 列配置里的关键参数逐个说表格里评分列的核心配置长这样{ field: rate, // 绑定的数据字段 title: 评分, // 表头显示名称 width: 180, // 列宽 align: center, // 对齐方式星星在列里居中才好看 sort: true, // 开启排序 templet: function(d) { return renderRate(d.rate); // 把数值渲染成星星HTML } }这几个配置各管一件事field指定了这列对应数据对象里的哪个属性排序时layui拿的就是这个属性的值。sort: true让表头出现可点击排序的箭头状态。没有这一项表头点了没反应。templet是一个模板渲染函数返回的HTML会替换掉这一列默认显示的纯文本。它只影响“显示效果”不影响底层数据结构。align: center对星星列挺重要。如果默认左对齐五颗星星挤在左边后面空一截视觉上很别扭。居中后跟分数数字组合展示更舒服。这里有一个非常关键的认知templet改变的是这一列展示出来的内容但sort排序比较的是原始字段值不会拿templet返回的HTML字符串去排序。所以我上面代码里即使templet把4.5分渲染成一串很长的星星HTML点击排序时layui实际比较的还是d.rate这个4.5。这一点一开始没搞清楚的话会出现很多诡异的“看起来没生效”的现象。2.2 渲染星星的三种写法写法一函数拼接HTML我最常用function renderRate(rate) { rate Number(rate) || 0; rate Math.max(0, Math.min(5, rate)); // 限制在0到5之间 var fullStars Math.floor(rate); var hasHalf rate - fullStars 0.5; var html ; for (var i 1; i 5; i) { if (i fullStars) { html i classlayui-icon layui-icon-rate-solid stylecolor:#ffb800;/i; } else if (hasHalf i fullStars 1) { html i classlayui-icon layui-icon-rate-half stylecolor:#ffb800;/i; } else { html i classlayui-icon layui-icon-rate/i; } } return html; }这个函数里做了三件小事把字符串数字转成数值把越界的评分限制在0到5之间根据数值算出满星数和是否需要半星。我在多个项目里都是这套逻辑简单、不依赖版本。写法二模板字符串有的项目里大家喜欢用模板字符串写templet比如templet: div{{ d.rate }}/div。但对于评分列我不推荐这种写法因为评分值需要先处理成星星模板字符串做这种逻辑转换不够直观一旦加半星、边界判断模板里会堆一堆表达式不好维护。写法三表格渲染完成后再初始化rate组件如果你确实需要用户直接在表格里点击星星打分可以这样templet里先输出一个空的div占位然后在table的done回调里循环初始化rate组件。done: function(res) { // 遍历表格的每一行找到评分列对应元素初始化rate组件 }这种方式能做出来但我个人不太建议在表格里直接做打分交互。表格行密度高星星点击区域小误操作率高而且一旦表格reload或者翻页所有rate组件都需要重新初始化处理不好还会出现重复实例。真需要打分功能我都会用弹层打开一个独立的表单页面在弹层里用原生rate组件体验更好代码也更干净。2.3 理解sort的底层比较规则layui表格的中文文档里对排序讲得不算细我实际测试下来的结论是前端排序时layui会把field字段对应的值取出来按照JavaScript默认的比较方式排。数值类型直接按大小处理字符串类型按Unicode编码逐个字符比较。这个机制有个经典坑接口返回的rate是字符串“4.5”时排序看起来正常但如果出现两位数的评分比如某个数据是“10”那字符串排序会把“10”排在“4”前面因为“1”的编码比“4”小。如果rate固定是0到5之间的评分一位数居多字符串排序勉强能看但我见过有人把“100分制”的分数也这样排结果整个排序乱掉。所以我的强烈建议是在数据源头就把rate处理成数字。后端接口返回时就确保字段值是数值类型如果是前端mock数据也注意别加引号。这样前端排序不需要任何额外处理。如果因为接口限制拿到的就是字符串那就在sortMethod里做转换。layui的列配置支持自定义排序方法{ field: rate, sort: true, sortMethod: function(a, b, type) { var diff Number(a) - Number(b); return type asc ? diff : -diff; } }这样点击表头时会自动用这个函数里的规则替代默认的字符串比较把值先转成数字再排。3. 实操过程完整实现3.1 初始化表格和模拟数据我统一用layui 2.8.x的写法来演示。第一步先把表格基本结构和数据准备好。!DOCTYPE html html head meta charsetutf-8 title表格星级排序示例/title link relstylesheet hrefhttps://unpkg.com/layui2.8.0/dist/css/layui.css /head body div stylepadding: 20px; table idcourseTable lay-filtercourseTable/table /div script srchttps://unpkg.com/layui2.8.0/dist/layui.js/script script layui.use([table, jquery], function() { var table layui.table; var $ layui.jquery; // mock数据注意rate是数字类型 var dataList [ {id: 1, courseName: 前端工程化实战, rate: 4.5, students: 120}, {id: 2, courseName: UI设计系统入门, rate: 3.0, students: 90}, {id: 3, courseName: 数据结构与算法, rate: 5.0, students: 150}, {id: 4, courseName: 数据库设计与优化, rate: 4.0, students: 80}, {id: 5, courseName: 操作系统原理, rate: 3.5, students: 200} ]; table.render({ elem: #courseTable, id: courseTable, data: dataList, cols: [[ {field: id, title: ID, width: 80, align: center}, {field: courseName, title: 课程名称, minWidth: 200}, {field: rate, title: 评分, width: 200, sort: true, align: center, templet: function(d) { return renderRate(d.rate); }}, {field: students, title: 学习人数, width: 120, sort: true, align: center} ]] }); }); function renderRate(rate) { rate Number(rate) || 0; rate Math.max(0, Math.min(5, rate)); var fullStars Math.floor(rate); var hasHalf rate - fullStars 0.5; var html span classrate-cell; for (var i 1; i 5; i) { if (i fullStars) { html i classlayui-icon layui-icon-rate-solid stylecolor:#ffb800;/i; } else if (hasHalf i fullStars 1) { html i classlayui-icon layui-icon-rate-half stylecolor:#ffb800;/i; } else { html i classlayui-icon layui-icon-rate/i; } } html span stylemargin-left:6px;color:#999;font-size:12px; rate.toFixed(1) /span; html /span; return html; } /script /body /html这里我故意在评分后面加了一个小数文本方便用户看清具体的分数。星星是给直觉看的数字是给精确信息看的两者组合是评分列比较实用的形态。3.2 纯前端排序一行代码都不用多写在上面的配置里rate列已经有sort: true数据又是本地一次性渲染的所以现在点击“评分”表头就已经能排序了。layui内部会自动切换asc、desc、空三种状态显示箭头图标并重排当前数据。如果你想进页面就默认按评分降序排不需要用户再点一下可以在table.render里加一个initSorttable.render({ elem: #courseTable, id: courseTable, data: dataList, initSort: {field: rate, type: desc}, // 默认按评分降序 cols: [[...]] });initSort里的field必须对应列配置里的fieldtype是asc或desc。加上之后表格第一次渲染就会带上表头的降序箭头数据按评分从高到低排列。这在实际项目中几乎是必配的因为产品经理通常希望用户打开的瞬间就能看到“最高分在前”。纯前端排序适合数据量不大的场景。实测中如果本地数据有几千条点击排序会有轻微卡顿但能接受。再往上走就建议换成后端排序了。3.3 后端排序监听sort事件才是核心后端排序里sort: true依然要加但更重要的是监听表格的sort事件并把排序条件交给服务端。layui.use([table, jquery], function() { var table layui.table; var $ layui.jquery; table.render({ elem: #courseTable, id: courseTable, url: /api/course/list, page: true, autoSort: false, // 关闭layui自动排序由sort事件统一处理 initSort: {field: rate, type: desc}, cols: [[ {field: id, title: ID, width: 80, align: center}, {field: courseName, title: 课程名称, minWidth: 200}, {field: rate, title: 评分, width: 200, sort: true, align: center, templet: function(d) { return renderRate(d.rate); }}, {field: students, title: 学习人数, width: 120, sort: true, align: center} ]] }); // 监听排序事件 table.on(sort(courseTable), function(obj) { // obj.field: 当前排序列的字段名 // obj.type: asc / desc / null不排序 table.reload(courseTable, { initSort: obj, // 把当前排序状态同步到表头箭头 where: { sortField: obj.field, sortOrder: obj.type }, page: {curr: 1} // 排序后回到第一页 }); }); });这段代码里我加了autoSort: false它的作用是关闭layui在点击表头时自动发起的排序请求。如果不关layui会自己把排序参数塞进请求里但参数名是它的固定规则后端未必按那个名字解析。关了之后所有请求参数完全由where里我自定义的sortField和sortOrder控制后端对接起来更明确。服务端要做的很简单接收sortField和sortOrder把它拼到查询逻辑里。比如Java后端可能就是SELECT * FROM course ORDER BY rate DESC LIMIT ?, ?这里的rate就是sortFieldDESC对应sortOrder。注意一点排序字段不要直接拼进SQL造成注入风险最好用一个白名单map做映射只允许后端预定义的字段参与排序。比如只允许rate、students、id这几个字段传进来其他字段一律忽略。还有分页参数。layui默认会传page和limit后端直接用来做分页就行。加了page: {curr: 1}的意思是排序操作后重新从第一页加载这样用户在第五页点排序时不会继续停留在第五页看一个重新排序后的局部结果。3.4 把“评分人数”综合排序做进去真实业务里评分排序往往不是单一维度。比如一个课程4.5分只有2个人学另一个课程4.3分有500人学单纯按评分排前者永远在前面但这显然不是管理者想看的“好课”。常规做法是引入一个综合分比如综合分 评分 * 0.7 人数排名权重 * 0.3算好之后按综合分排序。我在前端实现过一种简单的版本。接口返回原始字段表格渲染后在done回调里给每条数据追加一个score字段再把这个字段作为排序列done: function(res) { res.data.forEach(function(item) { // 综合分评分占70%学习人数占30%也可以自定义规则 item.score (Number(item.rate) * 0.7 Number(item.students) * 0.3).toFixed(2); }); }列配置里加一列{field: score, title: 综合评分, width: 220, sort: true, align: center, templet: function(d) { return renderRate(d.rate) span stylemargin-left:8px;color:#888; d.score /span; }}这样表格里展示的还是星星加数字但排序依据已经变成了综合分。这个思路可以延伸到很多场景评分加销量、评分加好评率、评分加更新时间等。计算综合分可以在前端做也可以在后端做。数据量大时一定要在后端算前端算只适合本地数据。4. 常见问题与排查技巧实录4.1 点击表头排序完全没反应问题现象表头没有箭头或者点击后什么都不发生。排查顺序我建议这样来列上有没有写sort: true漏了这句话表头根本不会进入排序状态。表格是否配置了url和page如果是且设置了autoSort: false那么点击表头本身不会自动刷新数据必须监听table.on(sort(...))并在里面table.reload否则点击后看起来就是“没反应”。检查lay-filter的值是否匹配。table.on(sort(courseTable))里的courseTable必须跟表格的lay-filtercourseTable完全一致少一个字母都监听不上。这三个是我遇到最多的原因排查效率很高。4.2 星星渲染成一堆小方块或者乱码星星实际是字体图标渲染异常基本都是字体文件没加载出来或者CSS没引对。检查一下页面有没有正确引入layui.css。如果用了CDN确认网络能访问到字体文件。另外类名容易写错我经常手滑把layui-icon-rate-solid写成layui-icon-rate-solied这种错不会报任何错误就显示一个空白方块。还有一个小细节有些精简版layui会把不用的图标样式删掉如果自制了精简包确认layui-icon-rate、layui-icon-rate-solid、layui-icon-rate-half这几个类名对应的伪元素content还在。4.3 排序结果跟页面看到的不一致这个前面也提到过核心原因就是数据类型。接口返回的rate如果是字符串“4.5”前端排序按字符串比较结果可能有偏差尤其是出现两位数字的时候。排查方法很简单在console里打印一两条数据看rate类型是number还是string。如果后端不好改就在前端用sortMethod包一层Number转换。另一个容易被忽视的是如果templet里对评分做了Math.floor之类的处理但排序用的是原始分数页面上看到的星数相同排序却可能分先后。比如4.4分和4.2分显示的都是4颗星但4.4会排在4.2前面。实际上这不是bug而是排序粒度比显示粒度更细如果你希望“显示几颗星就按几颗星排”需要把用于排序的字段也改成取整后的值比如在数据预处理时直接对rate取整。4.4 分页情况下每页内部排序了但整体顺序没变典型的前端排序配合后端分页导致的问题。数据只加载了几十条前端排序只能排这几十条翻到第二页又是另一批数据整体看顺序是乱的。解决办法只有一条换成后端排序也就是第3.3节那套逻辑。这个坑我在第一个项目就踩过当时被测试怼了半天后来学乖了只要开了分页排序一律走后端。4.5 表格刷新后评分列的星星不见了这个问题一般出现在用“写法三”初始化rate组件的场景。表格reload之后原本的rate实例还在但对应的DOM已经被新表格替换掉于是星星区域空白或者报错。我现在的建议是展示型评分一律用纯HTML拼接不要在done回调里做rate组件初始化。如果必须做成可交互打分单独抽个弹层页面在弹层里用原生rate组件避开表格刷新带来的生命周期问题。4.6 排序状态在翻页后丢失点了一次表头降序翻到第二页发现表头箭头消失排序状态没保存。处理方式是在table.reload时把initSort带上比如后端排序方案里那样table.reload(courseTable, { initSort: obj, where: { sortField: obj.field, sortOrder: obj.type }, page: {curr: 1} });这样每次请求都带着排序条件翻页时箭头也正常显示。5. 实操心得与避坑建议这个功能做完我自己的几点体会是评分字段一定要在数据源头就固定成数字类型这能省掉后面一整套排序兼容问题展示型评星星就用纯HTML拼省心且不依赖组件生命周期排序方案的选择在动手前就定好开了分页就别再用前端排序硬扛。另外建议在联调阶段做一次“脏数据测试”给接口塞几条评分超出0到5范围的记录比如6分、-1分、null看看前端渲染会不会崩。我在renderRate里做了Math.max和Math.min兜底就是吃过这个亏之后补上的。如果后续想再扩展可以考虑把评分筛选也一起做进去用一个下拉框让管理员只看3星以上的课程再配合排序后台筛数据效率会高很多。这个方向跟今天的排序功能共用一套字段配置扩展起来很顺手。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑