简介:这是一份面向微信小程序开发者的半星评论功能实现资源,采用前后端一体的完整代码结构,帮助解决传统五星评分无法表达精细满意度的问题。资源共15个文件,包含3个JS逻辑文件、3个PNG界面素材、2个JSON配置、2个WXSS样式文件及1个WXML页面结构,另有服务器对接说明与运行辅助脚本,压缩包仅19KB,轻量易部署。功能实现覆盖UI设计、bindtap事件监听、组件状态更新、0.5与1的评分逻辑判断、wx.cloud云数据库存储、API接口对接及异常处理等关键环节,样式层与逻辑层分离,便于二次开发。内容预览显示包含pages页面目录、utils工具模块及app.js全局逻辑,符合小程序标准项目结构。已有202人浏览学习,适合正在开发评价系统、需要精确打分交互的中初级小程序开发者参考,可直接对照代码理解半星评分的完整交互流程与数据同步方案。
1. 半星评论:小程序评分里被低估的交互细节
做小程序评价功能的时候,很多团队第一版只会做整星评分:五个星星,点第几颗就是几分。但真到课程评价、上门服务、商品晒单这些场景里,用户经常在 4 分和 5 分之间犹豫——觉得东西不错,又没好到非给满分不可,这时候被迫点 5 星是违心的,点 4 星又显得太苛刻。半星评论就是把评分粒度从 1.0 缩小到 0.5,让用户在“接近但没完全达到”的位置落点,一句话说透:评分不是打分,而是表达满意度梯度。
这个需求看起来只是星星中间多了个半星状态,但你真动手做的时候会发现,它牵扯到触摸判定、浮点存储、列表渲染、聚合计算一整条链路。只把样式改成“中间有半个星”远远不够,后端不配合半星值,前端做出来也会在提交环节翻车。这篇笔记适合正在自建评分体系的小程序前端,也适合要把评分数据接入统计报表的后端开发。我按自己踩过的路径来写:先讲清楚交互模型,再给能直接复用的实现代码,最后列排坑清单。
2. 半星评分的交互模型与取值设计:为什么说“点一下出 0.5”没那么简单
2.1 半星交互的三种主流形态:点选、滑动、保留整星再细分
半星评论最核心的问题不是画图,而是“用户用手怎么表达半星”。我接手过三个不同项目的评分组件,交互形态基本就三种,各有适用场景。
| 交互形态 | 操作方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 整星点选 + 二次细分 | 先点整星,再点同一位置切换半星 | 实现简单,误触率低 | 操作步骤多,首次上手不直观 | 表单提交页、后台评价页 |
| 触摸滑动连续取值 | 手指在星星区域左右滑动,经过区域实时变亮 | 反馈直观,一步到位 | 坐标换算逻辑复杂,小屏上容易误触 | 商品评价、直播打分 |
| 双态切换(整星长按变半星) | 短按整星,长按半星 | 单手可操作 | 长按时间难把握,用户学习成本高 | 工具类小程序,低频操作 |
我自己的项目里最终选了滑动取值:用户在 row 上从左到右滑动,手指位置决定亮几颗星,超过半个星就算半星。这个交互在短视频平台里已经被教育过一轮,用户天然知道“往右滑是高分”,接受度最高。如果你做的是后台管理端这种低频率操作,用整星点选再加半星切换更稳,滑动反而容易因为手抖误伤。
2.2 半星取值的浮点陷阱:0.5 在 JavaScript 里不是 0.5
写代码之前必须先把数据模型定死。常见做法是把评分值存成4.5、3.5这种带一位小数的数字,但 JavaScript 里4.5本身是安全浮点数,陷阱出在计算过程里。
const normalHalf = 4.5; const falseHalf = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1; // 等于 0.9999999999999999 const checkValue = 4.5 - 4; // 0.4999999999999996逻辑说明:4.5这种在二进制里刚好可以精确表示的值没问题,但是在做差值运算、累加平均、乘以比例系数的时候,很容易得到0.4999999999999996这种结果。如果你拿这个值去判断>= 0.5做半星渲染,边界上会被错误吞掉。
参数说明:我一般定两条规则,第一,组件内部所有计算都用「半星计数」,即4.5换算成整数9(9 个半星),3.0换算成6;第二,只在渲染到星星图层的瞬间把半星计数换算成浮点数。整数计算在 JavaScript 里是精确的,这能规避掉绝大多数浮点坑。
2.3 组件内部结构:视觉层、计算层、事件层分开
半星组件很容易写成一段 wxml 塞到底,我当时的第一版就是,后面维护差点没把自己逼疯。更合理的结构是三段式:视觉层只负责“根据数值画星星”,计算层负责“把触摸坐标换算成半星计数”,事件层负责“把半星计数抛给父组件”。
// 半星计数的核心换算函数 function pointToHalfStar(containerWidth, touchX, maxStars) { // 每个星星的宽度占比 const starUnitWidth = containerWidth / maxStars; // 触摸点在第几个星星范围内:0 到 maxStars * 2 的半星计数 const rawHalfIndex = Math.floor((touchX / starUnitWidth) * 2); // 夹紧到合法范围:最小 0(无星),最大 maxStars * 2(全星) const clamped = Math.min(Math.max(rawHalfIndex, 0), maxStars * 2); return clamped; }逻辑说明:starUnitWidth是单颗星星的像素宽度,touchX是触摸点相对星星容器的 X 坐标。把一个星星等比切成两半,触摸点落在左半就是奇数半星计数,落在右半就是偶数。rawHalfIndex算出来之后必须夹紧,不然用户滑到容器外面会得到负数或者超过满分的值,导致渲染层数组越界。
参数说明:maxStars默认传5,评论区如果不想要半星,直接把Math.floor换成Math.round,就能无缝切成整星模式。这套换算逻辑只依赖容器宽度和触摸 X 坐标,不依赖具体组件框架,小程序和 Web 端可以复用同一份算法。
3. 从零实现半星评分组件:画星星、算半星、绑手势的完整代码
3.1 第一步:用 WXML 和 CSS 画出可复用的星星
很多人纠结半星怎么画,有的用两张图片拼接,有的用 SVG,我推荐用字体图标加 CSS 裁剪。两层星星叠在一起:底层灰色整星,上层黄色整星用宽度百分比裁剪,裁到 50% 就是半星,裁到 90% 就是 4.8 分的视觉效果,一个样式函数全部搞定。
<view class="rate-container" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove"> <block wx:for="{{halfStarCount}}" wx:key="index"> <view class="star-wrap"> <text class="star star-bg">★</text> <text class="star star-front" style="width: {{starPercent}}%;">★</text> </view> </block> </view>.star-wrap { position: relative; display: inline-block; width: 40rpx; height: 40rpx; margin-right: 8rpx; overflow: hidden; } .star { font-size: 40rpx; line-height: 40rpx; position: absolute; left: 0; top: 0; } .star-bg { color: #e5e6e6; } .star-front { color: #ffb400; overflow: hidden; white-space: nowrap; }逻辑说明:wx:for遍历的是数组,数组长度等于“已经完整点亮的星星颗数”,比如 4.5 分就遍历 4 颗整星。第 5 颗星通过star-front的width: 50%来控制显示一半。这套写法的关键是star-front必须有overflow: hidden,否则宽度裁剪会失效。
参数说明:星星大小和间距我直接用40rpx加8rpx,在小程序里 rpx 会自动适配屏幕宽度。如果你要兼容极端窄屏,建议把星星大小改成由外部传入的size属性,不要写死在 CSS 里——这一步在整星组件里无所谓,半星对手势判定影响很大,星星越宽,半星判定区域越宽松。
3.2 第二步:绑定触摸事件,把坐标换算成半星值
上一步只是静态渲染。要让它动起来,核心是把touchmove事件里的touches[0].clientX换算成半星计数,这里有一个几乎所有新手都会踩的坑:clientX是相对于视口的坐标,星星容器可能在页面的任何位置,必须减掉容器左侧偏移。
Component({ data: { halfStarCount: 0, starPercent: 100, fullStarArray: [] }, methods: { onTouchMove(event) { const query = this.createSelectorQuery(); query.select('.rate-container').boundingClientRect(rect => { if (!rect) return; const touchX = event.touches[0].clientX - rect.left; const maxStars = this.data.maxStars || 5; const containerWidth = rect.width; const halfCount = this.pointToHalfStar(containerWidth, touchX, maxStars); this.updateDisplay(halfCount); }).exec(); }, pointToHalfStar(containerWidth, touchX, maxStars) { const starUnitWidth = containerWidth / maxStars; const rawHalfIndex = Math.floor((touchX / starUnitWidth) * 2); const clamped = Math.min(Math.max(rawHalfIndex, 0), maxStars * 2); return clamped; }, updateDisplay(halfCount) { const fullCount = Math.floor(halfCount / 2); const percent = (halfCount % 2) * 100; const fullStarArray = new Array(fullCount).fill(0); this.setData({ halfStarCount: fullCount, starPercent: percent || 100, fullStarArray }); } } });逻辑说明:boundingClientRect是在每次touchmove里实时获取容器位置,虽然频繁调用 selectorQuery 有性能损耗,但胜在容器左边距随页面滚动变化时不会算错。半星计数halfCount换算成展示层数据就两步:fullCount是完整星星数量,percent是最后一颗星显示宽度,奇数半星计数对应 50% 宽度。
参数说明:这里有个性能权衡点,createSelectorQuery在真机上大约有几毫秒延迟,快速滑动时会感觉星星“追手指慢半拍”。如果你的星星组件只支持点击不支持滑动,可以把容器位置缓存到onReady生命周期里,只在首次渲染时查询一次,后续直接用缓存值。我实测 60 帧滑动下缓存方案肉眼无差别,滑动方案每次查询也基本能接受,但低端安卓机上会有可感知的滞后,建议滑动场景还是缓存。
3.3 第三步:把半星值绑定到表单并实时预览
组件不能只在内部亮星星,最终要把值交出去。我用triggerEvent把半星计数传到父页面,父页面负责转成浮点评分展示。
updateDisplay(halfCount) { // ... 上面已有的展示逻辑 ... const rating = (halfCount / 2).toFixed(1); this.triggerEvent('change', { rating, halfCount }); }// 父页面使用 <rate-stars bind:change="onRateChange" max-stars="{{5}}" />Page({ onRateChange(event) { const { rating } = event.detail; this.setData({ currentRating: rating, ratingText: `本次评分:${rating} 分` }); } });逻辑说明:.toFixed(1)把半星计数除以 2 转成一位小数字符串,这一步是故意的。前面说过浮点计算不可靠,但halfCount / 2都是整数除以 2,得到的结果只要是用.toFixed格式化输出,就不会出现4.499999这种问题。
参数说明:半星计数传给父页面的好处是父页面可以自己决定转成几位小数,如果未来要做 0.1 粒度的评分(比如 4.3 分),父页面不需要改组件代码。这是我后来踩过坑才加的接口设计——第一版组件直接传浮点数,想升级 0.1 粒度时连组件一起返工。
3.4 组件参数表:按你的场景调好再抄
这块是给直接照抄代码的人看的。每个参数都有默认值和边界说明,是我调了多轮才定下来的。
| 参数名 | 类型 | 默认值 | 说明 | 调参建议 |
|---|---|---|---|---|
| maxStars | Number | 5 | 星级总数 | 课程评价用 5,外卖配送用时可以改成 3 |
| allowHalf | Boolean | true | 是否允许半星 | 后台审核页面建议设为 false |
| readonly | Boolean | false | 只读模式 | 已提交评分展示场景用 true |
| size | Number | 40(rpx) | 星星字号 | 小屏容器建议 32rpx |
| gap | Number | 8(rpx) | 星星间距 | 间距影响半星判定边界,越大判定越窄 |
| disabledColor | String | #e5e6e6 | 未点亮颜色 | 深色主题要传浅色号 |
我一般建议allowHalf不要做成静态属性,做成「父页面动态传入 + 组件内部缓存」的组合。半星评论这个功能很多时候是运营策略的一部分——大促期间鼓励用户打高分就关掉半星,日常开放半星拉真实度,做成静态配置后每次改策略都要发版,很被动。
4. 半星数据的存储与聚合展示:数组拆整半,列表渲染补位
4.1 半星值在后端怎么存:整数放大存储比浮点字段强十倍
前端做完了,数据要入库。这一步很多人会直接把4.5存成数据库的float或decimal字段,然后聚合统计时 SQL 里写AVG(rating),看着很顺,但三个月后你会面临两个问题:不同语言拿到的浮点精度不一样,报表工具读出来可能变成4.499999;另一个是如果你要区分「4.5 分」和「4 分半」这种用户表达,浮点字段没有语义边界。
我现在的做法是存整数,用score字段存9(代表 4.5 分),10代表满分 5 分,数据库字段用TINYINT或SMALLINT。前端提交的时候把半星计数直接传上来,后端只校验范围在0到maxStars * 2之间,不做任何浮点换算。
-- 存储结构 CREATE TABLE review_rating ( id INT PRIMARY KEY, order_id BIGINT NOT NULL, half_star_count TINYINT NOT NULL COMMENT '半星计数,10表示满分5分,9表示4.5分', review_text VARCHAR(500), created_at DATETIME ); -- 聚合查询:算平均分,结果转成一位小数 SELECT ROUND(AVG(half_star_count) / 2, 1) AS avg_rating, COUNT(*) AS review_count FROM review_rating WHERE order_id BETWEEN 10001 AND 10099;逻辑说明:half_star_count直接存半星计数,平均分计算放到查询时做。AVG(half_star_count) / 2是整数均值除以 2,在 MySQL 里结果转成浮点后精度是可控的。最稳的是你可以在应用层先算整数均值,再手动除以 2 并拼字符串,这样彻底绕开数据库浮点转换问题。
参数说明:TINYINT范围是 0 到 255,对五颗星的半星计数(最大 10)冗余很大,但如果你把组件 maxStars 改到 10(很多直播评级场景),计数会到 20,TINYINT依然够用。唯一要注意的是不要存4.5乘以2的结果9.0,数据库方言不统一的情况下9.0可能被存成浮点,我见过一次血泪场面,同一套代码在 MySQL 正常,在某个云数据库上查出来是9和9.0混着。
4.2 列表页半星展示:一块「半星补位」逻辑解决样式平移
完成了写入,接下来是高频场景:商品评论区列表每一条都要显示用户打了几星。这里有一个特别容易翻车的展示细节——如果每条评论都用同一个组件渲染,4.5分和4分在列表页看起来星星宽度不一样,眼睛扫过去会感觉评分条长短不齐,但这只是视觉差异,不算 bug。真正的 bug 是半颗星星后面的文字标签错位,我见过因为组件根节点宽度不同导致「4.5 分」文字在部分机型上换行。
我的做法是列表页不直接复用评分组件,而是用一个轻量的展示函数:把半星计数拆成「整星数组 + 半星宽度」,用纯 CSS 渲染。
function formatStars(halfCount, maxStars = 5) { const fullStars = Math.floor(halfCount / 2); const hasHalf = halfCount % 2 === 1; const emptyStars = maxStars - fullStars - (hasHalf ? 1 : 0); return { fullStars, hasHalf, emptyStars }; }<view class="list-rating"> <text wx:for="{{item.fullStars}}" wx:key="*this" class="star full">★</text> <text wx:if="{{item.hasHalf}}" class="star half">★</text> <text wx:for="{{item.emptyStars}}" wx:key="*this" class="star empty">★</text> <text class="rating-num">{{item.ratingText}}</text> </view>逻辑说明:formatStars输出三部分:完整星数、是否有半星、空星数。渲染时按顺序输出,ratingText固定拼接在星星后面。每个星星宽度固定,这行布局就不会因为半星而抖动。这里半星直接用了一个字符样式,不用之前滑动组件里的双层裁剪,因为列表页没有交互,没必要加载整套手势逻辑,省掉一份 setData 开销。
参数说明:列表项很多的时候(超过 100 条),建议把这个格式化函数放到 DataSet 的 map 阶段一次算好,不要在 wxml 里调用函数,小程序模板函数每次渲染都会重新执行,在长列表下有性能损耗。我见过团队把formatStars写在 wxml 里当过滤器用,页面滚起来卡得明显,挪到数据预处理后流畅多了。
4.3 评分数值计算:半星的均值、分布和一星二星加权
评分数据拿来做报表,统计口径要在设计之初就统一。半星的引入会让「好评率」计算变得尴尬——4.5 分算好评还是不算?我的处理方案是引入两个口径,默认展示「好评率 = 4 星及以上(含 4.5)评论数 / 总评论数」,同时在产品详情页展示「平均分」时用真实均值。为什么不用 4.5 以上算好评?因为很多用户打 4.5 其实是觉得「还行」,心理预期并没有到推荐程度,把 4.5 算进好评会拉高虚高的好评率,运营看着开心,用户复购时落差反而更大。
除了均值,我强烈建议把评分分布图也做了。半星的分布图比整星分布图有信息量得多,整星分布容易出现「4 星和 5 星两座大山」,半星分布能看到 4.5 这个中间地带的真实用户量。
SELECT half_star_count, COUNT(*) AS cnt FROM review_rating WHERE product_id = 12345 GROUP BY half_star_count ORDER BY half_star_count DESC;逻辑说明:GROUP BY half_star_count直接按半星计数分组,得到的是 0 到 10 共 11 档的分布。前端展示时可以把相邻两个计数合并成一颗星的宽度,比如 9 和 10 合并成一个柱,但柱内用渐变区分,视觉上更细腻。参数说明:做分布图时注意空档,很多评分档位是 0 条,前端要补齐显示而不是跳过,否则柱子之间宽度不一致,用户会误解成数据缺失。
5. 半星评论组件避坑指南:翻车现场与修复方案
5.1 半星判定漂移:触摸点正好卡在星星左边缘
现象:用户想打 4 分,手指点在第五颗星星的最左侧边缘,结果组件判定成 4.5 分。用户反复试了几次,每次都差一点点,最后放弃直接打 5 星。
原因:pointToHalfStar用的是「超过左半就算半星」的规则,第五颗星的左边缘 1 像素也算 4.5。这个规则在逻辑上没错,但手指触摸有物理宽度,用户视觉上认为自己点的是「第四颗星星后面、第五颗星星前面」的空隙,实际触摸点已经落进第五颗星的左半像素区。
解决:我把判定规则改成「触摸点在星星左半区域内,取整颗星的上一档;只有超过星星中线才算半星」。具体实现是在pointToHalfStar里加一个偏移量delayThreshold,默认取星星宽度的 10%。
pointToHalfStar(containerWidth, touchX, maxStars) { const starUnitWidth = containerWidth / maxStars; const delayThreshold = starUnitWidth * 0.1; const adjustedX = touchX - delayThreshold; const rawHalfIndex = Math.floor((adjustedX / starUnitWidth) * 2); const clamped = Math.min(Math.max(rawHalfIndex, 0), maxStars * 2); return clamped; }逻辑说明:adjustedX把触摸点左移 10% 星星宽度,相当于给每个半星判定区加了死区。用户点在星星左边缘时实际进入的是上一个星星的右半区,视觉反馈是上一档评分。这个死区加到每个边界上,半星判定就稳定很多。
参数说明:0.1这个值是玄学调出来的,太小(0.03)在窄屏上没效果,太大(0.25)会让用户很难滑到半星。建议在不同尺寸真机上各测一遍,用导出二维码给三个人分别试,收集一下「想打 4.5 但打出来是 4」的反馈比例。
5.2 小屏点击穿透与组件层级覆盖
现象:iPhone SE 和部分安卓小屏机上,用户点星星的时候偶尔会触发页面滚动,或者误触到后面的返回按钮。评论提交页和展示页都出现过。
原因:评分组件容器没有阻止touchmove事件冒泡,手指在星星上滑动时,事件被页面级 scroll-view 同时接住,导致一边在调评分一边页面在滚,手指离开时组件拿到一个偏移量。
解决:在touchmove里显式调用catchtouchmove阻止冒泡,同时给容器加disable-scroll属性。如果是页面上层有浮层(比如半透明遮罩),把评分组件的z-index提到最高。
<view class="rate-container" catchtouchstart="onTouchStart" catchtouchmove="onTouchMove" style="z-index: 999; position: relative;">逻辑说明:catch前缀会阻止事件继续向上传播,position: relative配合z-index保证组件在悬浮元素之下时还是能正确接收触摸。如果组件被遮罩完全盖住,boundingClientRect拿到的宽度还在,但触摸坐标全部落在遮罩上,表现为星星不亮。这个时候要检查的是遮罩层级而不是组件代码。
参数说明:这个坑在 Web 端复用组件时也会出现,CSS 里加touch-action: none可以阻止浏览器默认的滚动或缩放,小程序里没有这个属性,必须靠catchtouchmove。我建议把容器事件都写成catch开头,不要用bind,这样一劳永逸。
5.3 提交值与展示值不一致:0.5 浮动误差导致后台少统计一档
现象:用户提交评分后立即看到展示是「4.5 分」,但后台列表展示出来是「4 分」,订单详情页又是「4.5 分」,同一单在不同页面显示不一致。
原因:前端组件提交的评分值是浮点4.5,后端某个接口用float接收,在某次序列化时变成4.499999,直接传给下一个页面。另一个页面从数据库读取时是4.5,两边渲染逻辑不一样——一个用Math.floor一个用Math.round。
解决:统一提交格式,组件 triggerEvent 时传字符串"4.5",后端存字符串。渲染时每个页面都走同一个格式化函数,不准各自比较浮点数。
// 统一格式化 export function normalizeRating(input) { const numeric = Number(input); if (isNaN(numeric)) return '0.0'; const halfCount = Math.round(numeric * 2); return (halfCount / 2).toFixed(1); }逻辑说明:normalizeRating先转数字,再乘 2 取整,再除以 2 格式化。任何输入4.499999、4.5、"4.50",都会先被换算成半星计数 9,再输出固定格式"4.5"。这一步对所有页面统一,展示不一致的问题从根源上消失。参数说明:Math.round而不是Math.floor,是为了兼容可能出现的4.499999这种因为浮点误差小于 0.5 的情况,如果误差大于 0.5,那说明前面数值本身有问题,不是格式化能解决的。
5.4 只读模式下用户重复点击导致评分被重置
现象:在「已评价」页面,组件设了readonly,但用户快速点击星星后,评分从 5 星变回 4 星,页面没刷新但值被改了。
原因:readonly只限制了touchmove和touchstart不处理坐标,但triggerEvent的change事件在按钮抬起时又触发了一次。组件内部把上一次的halfStarCount记录在 data 里,readonly 模式下事件截断但 data 被遗留代码误修改。
解决:在onTouchStart入口统一拦截 readonly 判断,并且在setData之前判断当前是否 readonly,是则直接 return。这个判断要放在所有方法调用链的源头,而不是每个子函数里。
onTouchStart(event) { if (this.data.readonly) return; // 继续原有流程 } onTouchMove(event) { if (this.data.readonly) return; // 继续原有流程 }逻辑说明:这在别人代码里可能是个低级失误,但当组件里混有滑块、键盘、语音输入等多事件源时很常见。我见过一个版本,半星评分组件里加了个「点击星星弹出提示气泡」的功能,气泡上有点击事件,结果气泡点击冒泡到容器,容器判定成新的评分。这个原则上也是事件源没隔离,在气泡容器上单独catchtap拦截即可。
6. 把半星评论做成可复用能力:参数化组件与验收清单
6.1 组件抽象:评分模型、视觉层、事件层的关系
做了三个项目之后,我把半星评分组件沉淀成一个带 npm 包管理的独立组件库,核心思路是把「评分模型」(有多少颗星、几档粒度)和「视觉呈现」(星星大小、颜色、间距)彻底分离。组件接收一个config对象,视觉属性、交互属性、数据属性全走配置,不再散在代码里。
在项目 A 里,课程评价用 5 星 + 半星,星星是橙色的;在项目 B 里,售后评分用 3 星 + 整星,星星是蓝色的。原先两个项目各有一套组件,合并成一个组件后,只需要传不同的config,底层半星换算逻辑完全复用。如果你已经按前面章节的代码实现了第一版,这个抽象就是把所有的this.data默认值挪到一个统一入口,不会动核心算法。
6.2 验收清单:半星组件上线前逐个过一遍
这是我自己用的验收表,每次改完组件都会跑一遍,省得线上翻车再修。
| 测试项 | 操作 | 预期结果 | 优先级 |
|---|---|---|---|
| 半星精度 | 触摸第五颗星中线右侧 5rpx | 显示 5 星 | 必测 |
| 半星精度边界 | 触摸第五颗星中线左侧 2rpx | 显示 4.5 星 | 必测 |
| 最小值 | 触摸第一颗星左半 | 显示 0.5 星,不低于 0 | 必测 |
| 最大值 | 触摸超出容器右侧 | 显示 5 星,不溢出 | 必测 |
| 只读模式 | 多次点击 | 值不变化 | 必测 |
| 浮点提交 | 打 4.5 分并提交 | 后台收到 "4.5" 字符串 | 必测 |
| 长列表渲染 | 100 条评分滚动 | 无卡顿、星星无抖动 | 建议测 |
| 窄屏适配 | iPhone SE/安卓 360px | 星星不完全贴边 | 建议测 |
6.3 一个习惯:把评分值当字符串传,不当 Number 传
最后分享一个我觉得最有价值的工程习惯:组件对外暴露的评分值全部用字符串,"4.5"而不是4.5。这不只是防浮点坑,更重要的是让调用方强迫思考——你是展示给用户,还是参与计算?展示直接拼字符串,计算先Number()再操作,两边不会混。这个习惯让我少踩了很多次评分统计口径不一致的坑。
另外现在这个组件我仍然遵循一条原则:滑动式半星交互一定要配一个「当前分值」的实时数字展示。用户滑到 4.5 的时候,星星旁边立刻出现 4.5,他才确认这个半星是自己要的。没有数字反馈的半星评论,在微信小程序这种小屏环境下,用户手指稍微偏一点就得重滑,体验分直接拉低。
希望这两年的踩坑经历能帮到你;下次你要加半星评论的时候,别急着去网上一段一段拼代码,先按这个思路把交互模型和存储字段定下来,组件反而是最后才该碰的东西。
本文还有配套的精品资源,点击获取