讨论两种思路
效果动图
颜色可调,可鼠标移动点击评分
图层定位
- 创建一个div标签装下☆☆☆☆☆作为底层图层
- 再创建一个div标签装下★★★★★作为覆盖图层
- 通过CSS的 position: absolute 将覆盖图层显示在底层图层上方,通过修改覆盖图层的宽度来显示评分多少
三元运算符
- 使用 v-for="i in 5" 创建 5 个span标签,分别装下一颗星
- 利用三元运算符控制 span 标签显示的星星为实心还是空心 i <= 当前停留星星索引 ? '★' : '☆'
共同思路
参数传递
- 使用 defineProps 接受一个默认评分值:score 和星星颜色: theme
defineProps是 Vue 3 中 Composition API 提供的一个功能,用于在组合式函数组件(Composition API)中定义和接收父组件传递的 props。它与 Vue 2 中使用的props选项功能类似,但在语法和使用方式上有所不同,更加适合组合式 API 的风格。
defineProps在组合式 API 函数组件中使用 props 更加简洁和直观。它不仅支持静态类型定义,还支持通过 TypeScript 或 JSDoc 注释进行类型检查。此外,结合withDefaults函数还可以为 props 提供默认值。掌握这些特性能够大大提高开发效率和代码的可维护性。
let props = defineProps({
value: Number,
theme: { type: String, default: 'orange' }
})
- 使用 defineEmits 接受一个修改评分的事件
defineEmits是 Vue 3 Composition API 提供的一个函数,用于在组合式 API 中定义和触发自定义事件。它类似于 Vue 2 中的$emit方法,但在组合式 API 中使用更加直观和方便。
let emits = defineEmits([
'update-rate'
])
自定义星星颜色
- 这里星星用的是emoji表情来实现,实际上也是文本,因此只需要改变文本color属性即可。为了便于管理我们可以创建一个对象用键值对来管理星星颜色。
const themeObj = {
orange: '#fa541c',
blue: '#40a9ff',
green: '#73d13d',
black: '#00ff00',
red: '#f5222d',
yellow: 'yellow'
}
- 定义一个变量,用于将接收到 theme 的值转换成CSS样式。
const fontStyle = computed(() => {
return `color: ${themeObj[props.theme]};`
})
- 将该样式绑定到父容器上,子元素可用继承父元素的文本颜色属性。
<div :style="fontStyle">
<!-- 两种思路实现 -->
</div>
鼠标移动评分
首先要实现这个功能,需要用到鼠标的三个触发事件: 移入 移除 点击.
以及三个对应参数: 传入的 value,用于记录当前鼠标停留在第几颗星星的索引 Index,v-for="(i in 5)" 的 i
1. 移入(mouseover): 将该事件添加到子元素上,当鼠标移动到 '空心星星' 上时我们需要将 '空心星星' 显示成 '实心星星' ,触发事件调用方法实时修改 Index 的值:
function mouseOver(i) {
Index = i
console.log("移动事件( i , index ): ", i, Index)
}
2. 移出(mouseout): 将该事件添加到父元素上,当鼠标离开了父元素的块级,将 Index 重置为 value的值,以实现取消评分的功能:
function mouseOut() {
Index = value
console.log("移出事件( index , value ): ", Index, value)
}
3. 点击(click): 将该事件添加到子元素上,当子元素被点击时触发 修改评分事件 ,Vue中组件通信是异步事件,我们需要等待修改value成功后再进行下一步。在父组件中的修改value的方法应该对接收到的value值做判断,这里不属于我们讨论的范畴就深入讨论:
const onRate = async () => {
await emits('update-rate', state.scoreIndex)
console.log("修改事件( index , value ): ", state.scoreIndex, props.value)
}
实现评分组件
用图层定位来实现
用v-for 生成一组空星和一组实星
<div :style="fontStyle">
<div class="rate" @mouseout="mouseOut">
// 空心底层图层
<span @mouseover="mouseOver(i)" v-for="i in 5" :key="i">☆</span>
// 实心覆盖图层
<span class="hollow" :style="fontWidth">
<span @click="onRate(i)" @mouseover="mouseOver(i)" v-for="i in 5" :key="i">★</span>
</span>
</div>
</div>
利用CCS样式将覆盖图层显示到底层之上
.rate {
position: relative;
display: block;
}
.rate span{
display: inline-block;
width: 1rem;
height: 22px;
cursor: pointer;
overflow: hidden;
}
.rate > span.hollow {
position: absolute;
display:block;
top:0;
left:0;
width:0;
overflow:hidden;
}
联合 js 控制覆盖图层的宽度
- 因为这里星星是 emoji表情 实际上来说就是文本,也就是 1em 大小,所以我们可以用 Index和em拼接组成CSS样式,并绑定给覆盖图层。
<span class="hollow" :style="fontWidth">
<span @click="onRate(i)" @mouseover="mouseOver(i)" v-for="i in 5" :key="i">★</span>
</span>
let fontWidth = computed(() => `width: ${Index}em;`)
完整代码
父组件调用示例请滑至最底部
用三元运算符来实现
只生成一组星星标签
我们用三元运算符判断当前标签的 i 是否满足 i <= Index,满足的显示实星,不满足显示空心。Index 需要给定一个初始值为传入的 value 的值,我们利用 onMounted() 来实现在组件完成初始渲染并创建 DOM 节点后运行赋值代码
onMounted(() => {
Index = props.value
})
Html部分代码
<h1 :style="fontStyle">
<div class="show" @mouseout="mouseOut">
<span v-for="i in 5" :key="i" @mouseover="mouseOver(i)" @click="onRate(i)">{{ i <= state.scoreIndex ? '★' : '☆'}}</span>
</div>
</h1>
CSS样式部分
将鼠标样式改为可点击样式
.show {
display: flex;
padding: 10px 20px;
background-color: #f5f5f5;
border-radius: 5px;
box-shadow: 0 0 5px #ccc;
}
.show>span {
cursor: pointer;
}
完整代码
父组件调用示例请滑至最底部
父组件调用示例
<template>
<div>
<Rate :value="score" theme="orange" @update-rate="update" />
</div>
</template>
import Rate from './components/Rate.vue'
import { ref } from 'vue'
let score = ref(3)
function update(num) {
if (num > 5 || num < 1) {
return
}
score.value = num
}