引言
评分功能在现代Web应用中具有广泛的应用场景和重要性。无论是电子商务平台上的商品评价,社交媒体中的内容打分,还是在线教育中的课程评分,评分系统都扮演着重要的角色。它不仅帮助用户快速了解产品或内容的质量,还能为平台收集用户反馈,提升服务质量和用户体验。一个直观、互动性强的评分组件,能有效提升用户的参与感和满意度。
相信大家在平时的生活中一定在某app上打过评分,比如点外卖给店家和骑手评分、打车给司机评分等等,今天呆同学就和大家一起来打造一个五星评分的功能,通过一个Vue组件实现。
思考
我们在打造这个五星评分组件前,我们需要它具备哪些功能呢?
- 首页我们需要星级评分能够出现在页面中
- 进行主题颜色定制,点亮星星
- 可以打分
- 当鼠标移入和移出但未评分,星星跟随鼠标变化,但原评分不变,当鼠标点击后评分成功
正文
分数显示
首先我们使用npm i vite
快速创建一个Vue项目,在components
组件文件夹中创建我们的Rate.vue
评分组件。
然后我们在父组件中引入我们的子组件,同时我们需要父组件向子组件传递数据,通过子组件展示出来。
html
// App.vue
<template>
<div>
// 传数值需要用冒号,否则传过来的就是字符串
<Rate :score="score"/>
</div>
</template>
<script setup>
// 显示打分
import { ref } from 'vue';
import Rate from './components/Rate.vue'
let score = ref(3);
</script>
<style lang="scss" scoped></style>
html
// Rate.vue
<template>
<div>
{{ rate }}
<div/>
</template>
<script setup>
import { computed, defineProps, ref } from 'vue';
let props = defineProps({
score: {
type: Number,
default: 0
},
})
let rate = computed(() => "★★★★★☆☆☆☆☆".slice(5 - props.score, 10 - props.score))
</script>
<style lang="css" scoped>
</style>
defineprops
接收父组件传过来的数据,通过computed
计算以及slice
的切割,获取到和评分对应的长度,我们可以让有基础评分3的星星显示在我们的页面上。
主题定制
现在我们需要给星星添加颜色的主题,这是为了区别其它评分的样式,你可以自己决定星星的样式。
首先我们在<Rate />
中添加主题theme
html
<Rate :score="score" theme="yellow"/>
然后我们回到子组件defineprops
中接收theme
,然后再给包含rate
的div
容器动态绑定样式
html
// 绑定样式
<div :style="fontStyle">
{{ rate }}
<div/>
// 接收theme
let props = defineProps({
score: {
type: Number,
default: 0
},
theme: {
type: String,
default: 'blue'
}
})
紧接着我们去定制主题颜色,并且通过fontStyle
去实现主题颜色
js
// 主题可定制性
const themeObj = {
black: '#000',
yellow: '#fadb14',
blue: '#40a9ff',
green: '#73d13d'
}
const fontStyle = computed(() => {
return `color: ${themeObj[props.theme]};`
})
动态绑定的变量返回的是一串css
样式,刚好与style=""
相匹配
实现可打分
在经过上述我们对打分需求的一个初步接触之后,我们现在需要实现真正的星级评分功能。那么该如何实现呢?
首先我们要知道。上述实现的一个评分,我们的准备的星星其实是一段字符串,我们只能实现静态打分的一个状态,无法实现用户通过鼠标来进行动态打分,因此,我将选择通过以下方法来实现。
我们分别准备一颗实星和一颗空星,并将它们分别放入两个span
标签中,并通过v-for
分别循环渲染出五颗星星,再将它们设置成行内块元素后,通过css使这十颗星星重叠在一起,这样就只显示五颗星星了。
html
// Rate.vue
<template>
<div :style="fontStyle">
<div class="rate" @mouseout="mouseOut">
<span class="star" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">☆</span>
<span class="yellow" :style="fontWidth">
<span class="star" @click="onRate(num)" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">★</span>
</span>
</div>
<!-- {{ rate }} -->
<!-- <button type="button" @click="onRate">按钮</button> -->
</div>
</template>
在App.vue
父组件中,我们自定义一个分数更新事件update-score
,并将函数update(num)
传给Rate.vue
子组件。
html
// App.vue
<template>
<div>
<Rate @update-score="update" :score="score" theme="yellow"/>
</div>
</template>
<script setup>
// 显示打分
import { ref } from 'vue';
import Rate from './components/Rate.vue'
let score = ref(3);
function update(num) {
score.value = num;
}
</script>
之后在子组件通过defineEmits
中声明自定义事件update-score
,并且创建函数onRate(num)
更新数据,那么这里的num
便会传回父组件。再定义一个私有化数据width
来保存更新后的数据。
js
let emits = defineEmits(['update-score']);
const onRate = (num) => {
emits('update-score', num);
console.log('emit', props.score);
}
// 私有状态 mousemove mouseout 改变
let width = ref(props.score);
我们通过相对单位em
以及私有变量width
来计算实星的颗数,也就是宽度。
js
const fontWidth = computed(() => {
return `width: ${width.value}em;`
})
接下来就是给星星绑定鼠标事件了,我们给实星的span
标签绑定mouseOver
和点击事件,其中点击事件绑定的就是onRate(num)
函数,而mouseOver
当鼠标移入时,星星亮起,给包裹实星和空星的容器div
绑定mouseOut
,当未选择评分鼠标移出时,恢复到原来的评分状态。
html
<template>
<div :style="fontStyle">
<div class="rate" @mouseout="mouseOut">
<span class="star" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">☆</span>
<span class="yellow" :style="fontWidth">
<span class="star" @click="onRate(num)" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">★</span>
</span>
</div>
</template>
<script setup>
import { ref } from 'vue';
const mouseOver = (num) => {
width.value = num;
}
const mouseOut = () => {
// 鼠标离开,没有做出决定,那么就恢复原来的状态
width.value = props.score;
}
完整的代码如下:
App.vue:
html
<template>
<div>
<Rate @update-score="update" :score="score" theme="yellow"/>
</div>
</template>
<script setup>
// 显示打分
import { ref } from 'vue';
import Rate from './components/Rate.vue'
let score = ref(3);
function update(num) {
score.value = num;
}
</script>
<style lang="scss" scoped></style>
Rate.vue:
html
<template>
<div :style="fontStyle">
<div class="rate" @mouseout="mouseOut">
<span class="star" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">☆</span>
<span class="yellow" :style="fontWidth">
<span class="star" @click="onRate(num)" @mouseover="mouseOver(num)" v-for="num in 5" :key="num">★</span>
</span>
</div>
<!-- {{ rate }} -->
<!-- <button type="button" @click="onRate">按钮</button> -->
</div>
</template>
<script setup>
import { computed, defineProps, defineEmits, ref } from 'vue';
let props = defineProps({
score: {
type: Number,
default: 0
},
theme: {
type: String,
default: 'blue'
}
})
// 主题可定制性
const themeObj = {
black: '#000',
yellow: '#fadb14',
blue: '#40a9ff',
green: '#73d13d'
}
// let rate = computed(() => "★★★★★☆☆☆☆☆".slice(5 - props.score, 10 - props.score))
const fontStyle = computed(() => {
return `color: ${themeObj[props.theme]};`
// color: props.theme === 'blue' ? '#1E90FF' : '#FF4500'
})
let emits = defineEmits(['update-score']);
const onRate = (num) => {
emits('update-score', num);
console.log('emit', props.score);
}
// 私有状态 mousemove mouseout 改变
let width = ref(props.score);
const fontWidth = computed(() => {
return `width: ${width.value}em;`
})
const mouseOver = (num) => {
width.value = num;
}
const mouseOut = () => {
// 鼠标离开,没有做出决定,那么就恢复原来的状态
width.value = props.score;
}
</script>
<style lang="css" scoped>
.star {
letter-spacing: 3px;
}
body {
font-family: sans-serif;
}
.rate {
position: relative;
display: inline-block;
}
.rate > span.yellow {
position: absolute;
display: inline-block;
top: 0;
left: 0;
overflow: hidden;
/* width: 3em; */
}
</style>
实现效果:
总结
通过结合 App.vue
和 Rate.vue
,我们实现了一个交互式五星评分组件。App.vue
负责管理评分状态并将其传递给 Rate.vue
,而 Rate.vue
则实现了评分组件的展示、鼠标悬停和点击评分功能。组件通过 defineProps
、defineEmits
和响应式变量管理评分数据,并支持主题颜色定制。整个实现展示了 Vue.js 的响应式特性和组件化开发的优势,代码结构清晰,易于维护和扩展。
那么以上便是五星评分这个组件与大家的分享,希望将来可以用在你写的项目中。