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

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

相关推荐
J总裁的小芒果7 分钟前
Vue3 el-table 默认选中 传入的数组
前端·javascript·elementui·typescript
Lei_zhen9610 分钟前
记录一次electron-builder报错ENOENT: no such file or directory, rename xxxx的问题
前端·javascript·electron
咖喱鱼蛋12 分钟前
Electron一些概念理解
前端·javascript·electron
yqcoder13 分钟前
Vue3 + Vite + Electron + TS 项目构建
前端·javascript·vue.js
鑫宝Code31 分钟前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
Mr_Xuhhh2 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋3 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿3 小时前
【前端】CSS
前端·css
ggdpzhk3 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•5 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html