轻松实现五星评分: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 的响应式特性和组件化开发的优势,代码结构清晰,易于维护和扩展。

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

相关推荐
麒麟而非淇淋4 分钟前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习17 分钟前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
蒟蒻的贤20 分钟前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf24 分钟前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
蘑菇头爱平底锅26 分钟前
十万条数据渲染到页面上如何优化
前端·javascript·面试
su1ka11129 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬31 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*31 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_8010741532 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_33 分钟前
报错合计-1
前端