轻松实现五星评分:Vue.js 组件开发指南

引言

评分功能在现代Web应用中具有广泛的应用场景和重要性。无论是电子商务平台上的商品评价,社交媒体中的内容打分,还是在线教育中的课程评分,评分系统都扮演着重要的角色。它不仅帮助用户快速了解产品或内容的质量,还能为平台收集用户反馈,提升服务质量和用户体验。一个直观、互动性强的评分组件,能有效提升用户的参与感和满意度。

相信大家在平时的生活中一定在某app上打过评分,比如点外卖给店家和骑手评分、打车给司机评分等等,今天呆同学就和大家一起来打造一个五星评分的功能,通过一个Vue组件实现。

思考

我们在打造这个五星评分组件前,我们需要它具备哪些功能呢?

  1. 首页我们需要星级评分能够出现在页面中
  2. 进行主题颜色定制,点亮星星
  3. 可以打分
  4. 当鼠标移入和移出但未评分,星星跟随鼠标变化,但原评分不变,当鼠标点击后评分成功

正文

分数显示

首先我们使用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,然后再给包含ratediv容器动态绑定样式

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.vueRate.vue,我们实现了一个交互式五星评分组件。App.vue 负责管理评分状态并将其传递给 Rate.vue,而 Rate.vue 则实现了评分组件的展示、鼠标悬停和点击评分功能。组件通过 definePropsdefineEmits 和响应式变量管理评分数据,并支持主题颜色定制。整个实现展示了 Vue.js 的响应式特性和组件化开发的优势,代码结构清晰,易于维护和扩展。

那么以上便是五星评分这个组件与大家的分享,希望将来可以用在你写的项目中。

相关推荐
燃先生._.11 分钟前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235241 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240252 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人3 小时前
前端知识补充—CSS
前端·css
GISer_Jing3 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245523 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v3 小时前
webpack最基础的配置
前端·webpack·node.js