Vuetify v-rating 星级评分组件完全指南:从基础用法到源码级原理
【免费下载链接】vuetify🐉 Vue Component Framework项目地址: https://gitcode.com/gh_mirrors/vu/vuetify
v-rating是 Vuetify 中用于收集用户评分的专业组件,它以星形图标为交互载体,将"用户反馈"这一简单指标转化为直观、可无障碍访问的界面元素,广泛应用于电商评价、产品满意度调查、内容打分等场景。本文以 Vuetify 官方文档的 Ratings 页面为主体,结合本仓库packages/vuetify/src/components/VRating/下的源码实现与测试用例,系统讲解v-rating的全部核心配置项(颜色、密度、清空、只读、悬停、图标、半星增量、尺寸、无障碍标签)、两个插槽(item、item-label)的进阶用法,以及它背后的状态计算、键盘导航和 CSS 半星裁剪原理,让你既能"会用",也能"懂它为什么这么工作"。
组件定位与核心价值
v-rating是构建用户控件时一个专门但重要的部件。通过评分收集用户反馈,是一种简单的分析手段,却能为你的产品或应用提供大量有价值的信息。它本质上是一个"被视觉化包装的单选控件"——从源码看,每个评分项在内部渲染为一个隐藏的<input type="radio">(见 VRating.tsx),这保证了它在不依赖 JavaScript 的情况下依然具备原生表单语义,也决定了它与v-form、v-model等机制的自然融合。
从组件构成上看,VRating复用了多个 Vuetify 基础组件与组合式函数:
- 每个星形图标实际由
VBtn渲染(VRating.tsx),因此天然继承了按钮的尺寸、密度、涟漪(ripple)等能力; - 密度与尺寸分别来自
makeDensityProps与makeSizeProps(VRating.tsx),与 Vuetify 全局约定保持一致; - 主题支持通过
makeThemeProps与provideTheme接入(VRating.tsx)。
基础用法
v-rating提供了一个简单直观的接口来收集用户反馈。最基本的使用方式只需绑定v-model:
<template> <div class="text-center"> <v-rating v-model="rating"></v-rating> </div> </template> <script setup> import { ref } from 'vue' const rating = ref(3) </script>默认渲染 5 颗星,v-model的值为数值型评分。在官方文档的交互示例(usage.vue)中,你还可以实时切换half-increments、hover、readonly、length、size、color、active-color等配置来观察组件行为,并把生成的代码直接复制到你的项目中。
从源码角度理解这个"简单接口"背后的逻辑:
- 值规范化:
normalizedValue通过clamp(parseFloat(rating.value), 0, Number(props.length))把任意输入钳制在0 ~ length区间(VRating.tsx),即使你传入越界值也不会渲染出错; - 状态计算:
itemState依据"当前值 >= 星值"判断每颗星是否填充(isFilled),并据此决定使用fullIcon还是emptyIcon(VRating.tsx); - 事件绑定:
eventState为每个评分档位生成onMouseenter、onMouseleave、onClick处理器,其中点击回调会在disabled或readonly时直接短路返回(VRating.tsx)。
对应地,浏览器端测试 VRating.spec.browser.tsx 验证了"点击第 4 个评分项后v-model值变为 4""响应v-model外部变化重新渲染图标"等基础行为。
API 一览
v-rating是评分功能的唯一核心组件,其全部 props、slots 与事件在 VRating.tsx 中集中定义。下表汇总了所有可用配置项:
| Prop | 类型 | 默认值 | 说明 | | - | - | - | - | |model-value|number \| string|0| 当前评分值,支持v-model双向绑定 | |length|number \| string|5| 评分项(星星)的数量 | |color|string| — | 未选中/填充项的基础颜色 | |active-color|string| — | 已填充项的颜色,未设置时回退到color| |clearable|boolean|false| 点击当前评分值可将其重置为0| |readonly|boolean|false| 只读模式,禁止修改评分 | |disabled|boolean|false| 禁用整个组件 | |hover|boolean|false| 悬停时图标变为实心并略微放大 | |half-increments|boolean|false| 支持0.5粒度评分 | |item-labels|string[]| — | 每个评分项的标签文本数组 | |item-label-position|'top' \| 'bottom'|'top'| 标签显示在图标上方或下方 | |empty-icon|IconValue|$ratingEmpty| 未填充状态使用的图标 | |full-icon|IconValue|$ratingFull| 已填充状态使用的图标 | |item-aria-label|string|$vuetify.rating.ariaLabel.item| 供辅助技术使用的每项无障碍标签 | |ripple|boolean|false| 点击时是否显示涟漪效果 | |name|string| 自动生成 | 内部 radio 输入框的name属性 |
组件还会自动继承density、size、tag、theme以及通用组件属性(class/style等)。事件方面仅有一个update:model-value(VRating.tsx),用于配合v-model。
其中两个图标默认值$ratingEmpty与$ratingFull是 Vuetify 主题中注册的图标别名,会跟随当前图标的默认集(如 Material Design Icons)解析为具体图标,你也可以用任意自定义图标覆盖。
Props 实战详解
颜色:Color
v-rating的颜色可以随心定制,选中与未选中状态的颜色可以分别设置:active-color控制已填充星星的颜色,color控制未填充星星(以及作为其回退色)的颜色。
<template> <div class="text-center"> <v-rating v-model="rating" active-color="blue" color="orange-lighten-1" ></v-rating> </div> </template> <script setup> import { ref } from 'vue' const rating = ref(3) </script>在源码中,activeColor = props.activeColor ?? props.color,而每颗星的实际颜色由(isFilled || isHovered) ? activeColor : props.color决定(VRating.tsx)——即填充状态和悬停状态都使用active-color。完整的官方示例见 prop-color.vue。
密度:Density
使用densityprop 控制v-rating各项在垂直方向占用的空间,可选值default、comfortable、compact:
<template> <div class="d-flex flex-column align-center justify-center"> <v-rating v-model="rating" class="ma-2" density="default"></v-rating> <v-rating v-model="rating" class="ma-2" density="comfortable"></v-rating> <v-rating v-model="rating" class="ma-2" density="compact"></v-rating> </div> </template>该 prop 直接透传给内部每个VBtn(VRating.tsx),因此密度的视觉表现与 Vuetify 其他组件保持一致。完整示例见 prop-density.vue。
清空:Clearable
点击当前评分值可以将评分重置为0:
<template> <div class="text-center"> <v-rating v-model="rating" clearable></v-rating> </div> </template> <script setup> import { ref } from 'vue' const rating = ref(3) </script>其实现逻辑一行即可说明:rating.value = normalizedValue.value === value && props.clearable ? 0 : value(VRating.tsx)——当再次点击当前值且开启了clearable时归零,否则设置为新值。测试用例 VRating.spec.browser.tsx 验证了"未开启时点击已选值不变化、开启后点击同一项归零"的完整行为。
只读:Readonly
对于不允许修改的评分展示(如只读的商品平均分),使用readonlyprop:
<template> <div class="text-center"> <v-rating v-model="rating" readonly></v-rating> </div> </template>源码中readonly与disabled会在点击与键盘事件处理器中直接返回(VRating.tsx 与 L157),同时根元素会加上v-rating--readonly类。样式层通过.v-rating--readonly { pointer-events: none }直接屏蔽所有指针交互(VRating.sass),实现双重防护。测试用例分别验证了只读状态下点击与方向键都不会改变值(VRating.spec.browser.tsx)。
悬停效果:Hover
使用hoverprop 后,鼠标悬停到的评分图标会变为实心颜色,并略微放大:
<template> <div class="text-center"> <v-rating v-model="rating" hover></v-rating> </div> </template>放大效果来自样式层:悬停时.v-rating--hover作用域下的按钮应用transform缩放(VRating.sass),该 transform 值定义在 _variables.scss 中。悬停状态由hoverIndex追踪:鼠标进入某个档位时记录下标,离开时重置为-1(VRating.tsx)。测试 VRating.spec.browser.tsx 通过userEvent.hover验证了悬停第 3 个图标后前 3 个图标全部变为实心。
标签:Labels
v-rating可以在每个评分项的上方或下方显示标签,通过item-label-position控制位置(默认top):
<template> <div class="d-flex align-center justify-center flex-column"> <v-rating v-model="rating" :item-labels="['sad', '', '', '', 'happy']" class="ma-2" item-label-position="top" ></v-rating> <v-rating v-model="rating" :item-labels="['sad', '', '', '', 'happy']" class="ma-2" item-label-position="bottom" ></v-rating> </div> </template>item-labels是长度与length对应的字符串数组,空字符串会渲染为占位空格以保持对齐(VRating.tsx)。完整示例见 prop-item-labels.vue。在showcase测试故事中也有对应演示(VRating.spec.browser.tsx)。
图标:Icons
可以使用自定义图标来替换默认的星形。图标由三个 prop 控制:empty-icon(未填充)、full-icon(已填充),配合half-increments时半星图标通过 CSS 裁剪实现(见下文"半星增量"),因此并不需要一个单独的half-iconprop:
<template> <div class="text-center"> <v-rating v-model="rating" empty-icon="mdi-circle-outline" full-icon="mdi-circle" half-increments hover ></v-rating> </div> </template>图标值支持 Vuetify 的IconValue类型,即可以是图标名字符串,也可以是渲染函数或 SVG 组件(VRating.tsx)。完整示例见 prop-icons.vue。
数量:Length
通过lengthprop 改变评分项的数量:
<template> <div class="text-center"> <v-rating v-model="rating" length="10"></v-rating> </div> </template>源码中使用createRange(Number(props.length), 1)生成从 1 到 length 的序列(VRating.tsx),配合normalizedValue的钳制逻辑,length 也可以在运行时动态变化。示例见 prop-length.vue。
半星增量:Half increments
half-incrementsprop 提升了评分粒度,允许出现.5这样的值:
<template> <div class="text-center"> <v-rating v-model="rating" half-increments hover ></v-rating> <pre>{{ rating }}</pre> </div> </template>它的实现非常巧妙,值得展开说明:
- 档位翻倍:
increments会把每个整数档位展开为[v - 0.5, v],例如 5 颗星会生成 10 个可点击档位(VRating.tsx)。测试断言此时页面上出现 10 个隐藏 radio 输入框(VRating.spec.browser.tsx); - CSS 裁剪绘制半星:半星档位渲染时带有
v-rating__item--half类,样式层使用clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%)只显示图标左半部分,并配合绝对定位叠加在整星之上(VRating.sass),从而在视觉上呈现"左实右空"的半星效果; - 键盘步进:方向键的步长会随
halfIncrements变为0.5(VRating.tsx)。
测试验证点击第 4 个半星档位得到3.5分(VRating.spec.browser.tsx)。完整示例见 prop-half-increments.vue。
尺寸:Size
sizeprop 用于控制图标大小,可以复用v-icon的尺寸值,也可以传入任意数值:
<v-rating v-model="rating" size="64"></v-rating>size来自makeSizeProps,并直接透传给内部每个VBtn(VRating.tsx)。测试用例专门验证了half-increments与自定义size="64"组合使用时的行为(VRating.spec.browser.tsx)。官方交互示例 usage.vue 中可拖动滑块在 16~128 之间实时预览尺寸变化。
无障碍标签:Aria Label
通过item-aria-labelprop 为每个评分项提供辅助技术(屏幕阅读器)可读的标签:
<v-rating v-model="rating" :item-aria-label="(value) => `Rate ${value} out of 5`" ></v-rating>默认值为$vuetify.rating.ariaLabel.item(一个本地化文案键)。源码中该标签会同时渲染在隐藏文本v-rating__hidden中,并作为每个VBtn的aria-label(VRating.tsx 与 L217),确保辅助技术与普通用户获得一致的信息。文案通过useLocale的t函数按当前语言解析。
Slots 深度定制
插槽为评分组件提供了高级定制能力,让你完全掌控评分的展示形式。
Item 插槽
item插槽允许你完全替换每个评分项的默认渲染内容。插槽接收以下属性:
value:当前项的值index:当前项的索引isFilled:该项是否处于填充状态isHovered:该项是否处于悬停状态icon:当前应显示的图标color/activeColor:颜色信息props:透传给内部VBtn的完整按钮属性(含键盘与无障碍处理)rating:当前总分
<template> <div class="text-center"> <v-rating v-model="rating"> <template v-slot:item="props"> <v-icon :color="props.isFilled ? colors[props.index] : 'grey-lighten-1'" size="large" > {{ props.isFilled ? 'mdi-star-circle' : 'mdi-star-circle-outline' }} </v-icon> </template> </v-rating> </div> </template> <script setup> import { ref } from 'vue' const colors = ['green', 'purple', 'orange', 'indigo', 'red'] const rating = ref(4.5) </script>该示例让每颗填充的星星使用不同的颜色。注意:插槽内容默认仍然包裹在带点击/悬停/键盘事件的<label>中,评分交互依然生效,props中还携带了tabindex与键盘处理器供自定义按钮使用(VRating.tsx)。完整示例见 slot-item.vue,showcase故事中也有一个用VBtn渲染数字评分的变体(VRating.spec.browser.tsx)。
自定义标签插槽:item-label
item-label插槽让你在评分项旁展示任意内容。插槽接收{ value, index, label }属性,其中label来自item-labels数组中的对应项:
<template> <v-rating v-model="rating"> <template v-slot:item-label="props"> C{{ props.value }} </template> </v-rating> </template>当该插槽存在时,它会优先生效并覆盖item-labels数组的默认渲染(VRating.tsx)。测试中的展示故事将标签渲染为C1、C2等形式(VRating.spec.browser.tsx),示例文件见 slot-item-label.vue。
进阶场景:卡片评分
评分组件非常适合与产品类界面搭配,用于收集和展示客户反馈。官方文档提供了两个组合示例:
- misc-card.vue:将
v-rating嵌入v-card,结合v-avatar、v-list等组件展示单条用户评价; - misc-card-overview.vue:卡片式评分总览。
这类场景通常还会配合readonly(仅展示已有评分)或clearable(允许修改),并可参考 cards 与 icons 文档组合出更丰富的界面。文档中原本规划的 "Advanced usage" 示例(misc-advanced.vue,见 ratings.md 中的注释)目前仍处于注释状态,尚未正式开放。
键盘导航与无障碍设计
v-rating提供了完整的键盘操作支持,这也是它作为表单控件的重要一环:
- Tab 聚焦:当前评分值对应的项拥有
tabindex="0",其余项为-1,确保只聚焦一个锚点(VRating.tsx); - 方向键调节:
ArrowRight增加值(上限为length),ArrowLeft减少值(下限为 0),步长为 1 或半星模式下的 0.5,调节后焦点会跟随移动到新的当前项(VRating.tsx); - 空格选中:
VBtn自身的按钮语义天然支持空格触发点击; - 隐藏 radio 语义:每个档位对应一个隐藏的 radio 输入,配合
name属性构成标准的单选组,name未指定时自动生成v-rating-{uid}(VRating.tsx)。
浏览器测试 VRating.spec.browser.tsx 完整覆盖了"Tab 进入 → 空格评分 → Tab 离开 → Shift+Tab 返回 → 方向键调节并移动焦点"的整条键盘链路。
样式体系速览
v-rating的样式集中在 VRating.sass 与 _variables.scss 中,几个关键设计点:
- 根元素为
display: inline-flex且max-width: 100%,可随内容自适应; - 每个评分项外包一层
v-rating__wrapper,标签在顶部或底部时分别使用flex-direction: column与column-reverse实现上下布局; - 内部按钮为
plain变体并设置较低不透明度,填充/悬停时通过图标切换与transform缩放表达状态; - 半星通过
clip-path裁剪叠加实现; - 只读时以
pointer-events: none禁用指针交互。
总结
v-rating是 Vuetify 中一个"小而精"的组件:通过v-model与length、color/active-color、clearable、readonly、hover、half-increments、size、item-labels、自定义图标与无障碍标签等十余个配置项,即可覆盖从简单的五星打分到带半星、自定义图标、逐项标签和完全自定义渲染的复杂评分场景。其底层以隐藏 radio 输入 +VBtn图标按钮 + CSS 裁剪半星的组合实现,兼顾了表单语义、无障碍访问与视觉表现力,相关实现细节均可在 VRating.tsx、VRating.sass 与 VRating.spec.browser.tsx 中进一步查阅。配合卡片、图标等组件组合使用,即可快速构建专业的产品反馈界面。
【免费下载链接】vuetify🐉 Vue Component Framework项目地址: https://gitcode.com/gh_mirrors/vu/vuetify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考