组件化开发
渲染评级分数
对于简单的评级需求,就可以使用组件。这样,只需要一行代码就可以实现评级需求。比如下面的代码,rate 是 1 到 5 的整数,通过 slice 方法,我们直接渲染出对应数量的星星即可
Vue SFC
"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate)src/components/Rate.vue
Vue SFC
<template>
<div :style="fontstyle">
{{rate}}
</div>
</template>
<script setup>
import { defineProps,computed, } from 'vue';
let props = defineProps({
value: Number,
theme:{type:String,default:'orange'}
})
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
const themeObj = {
'black': '#00',
'white': '#fff',
'red': '#f5222d',
'orange': '#fa541c',
'yellow': '#fadb14',
'green': '#73d13d',
'blue': '#40a9ff',
}
const fontstyle = computed(()=> {
return `color:${themeObj[props.theme]};`
})
</script>html
<template>
<Rate :value="score"></Rate>
<Rate :value="3" ></Rate>
<Rate :value="4" theme="red"></Rate>
<Rate :value="1" theme="green"></Rate>
</template>
<script setup>
import {ref} from 'vue'
import Rate from '../components/rate.vue'
let score = ref(3)
</script>组件事件
在 Vue 中,使用 emit 来对外传递事件,这样父元素就可以监听 Rate 组件内部的变化。对 Rate 组件进行改造,首先星星都是普通的文本,没有办法单独绑定 click 事件。所以我们要对模板进行改造,每个星星都用 span 包裹,并且我们可以用 width 属性控制宽度,支持小数的评分显示
html
<template>
<div :style="fontstyle">
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
import {computed, defineProps, ref} from "vue";
let props = defineProps({
value: Number,
theme: {type: String, default: 'orange'}
})
let width = ref(props.value)
function mouseOver(i) {
width.value = i
}
function mouseOut() {
width.value = props.value
}
const fontwidth = computed(() => `width:${width.value}em;`)
const themeObj = {
'black': '#00',
'white': '#fff',
'red': '#f5222d',
'orange': '#fa541c',
'yellow': '#fadb14',
'green': '#73d13d',
'blue': '#40a9ff',
}
const fontstyle = computed(() => {
return `color:${themeObj[props.theme]};`
})
</script>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate > span.hollow {
position: absolute;
display: inline-block;
top: 0;
left: 0;
width: 0;
overflow: hidden;
}
</style>因为现在是通过宽度显示星星,所以我们还可以支持 3.5 分的小数评级,并且支持鼠标滑过的时候选择不同的评分
Vue SFC
<Rate :value="3.5" ></Rate>
在点击五角星选择评分的时候,把当前评分传递给父组件即可。使用 defineEmit 来定义对外“发射”的数据,在点击评分的时候触发即可
Vue SFC
<template>
<div :style="fontstyle">
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
import {computed, defineEmits, defineProps, ref} from "vue";
let props = defineProps({
value: Number,
theme: {type: String, default: 'orange'}
})
let width = ref(props.value)
function mouseOver(i) {
width.value = i
}
function mouseOut() {
width.value = props.value
}
let emits = defineEmits('update-rate')
function onRate(num){
emits('update-rate',num)
}
const fontwidth = computed(() => `width:${width.value}em;`)
const themeObj = {
'black': '#00',
'white': '#fff',
'red': '#f5222d',
'orange': '#fa541c',
'yellow': '#fadb14',
'green': '#73d13d',
'blue': '#40a9ff',
}
const fontstyle = computed(() => {
return `color:${themeObj[props.theme]};`
})
</script>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate > span.hollow {
position: absolute;
display: inline-block;
top: 0;
left: 0;
width: 0;
overflow: hidden;
}
</style>在下面的代码中,我们使用 @update-rate 接收 Rate 组件 emit 的数据,并且修改 score 的值,这样就完成了数据修改后的更新。
Vue SFC
<template>
<Rate :value="score"></Rate>
<Rate :value="3" ></Rate>
<Rate :value="4" theme="red"></Rate>
<Rate :value="1" theme="green"></Rate>
<h3>事件</h3>
<rateEvent :value="score" @update-rate="update"></rateEvent>
</template>
<script setup>
import {ref} from 'vue'
import Rate from '../components/rate.vue'
import rateEvent from '../components/rateEvent.vue'
let score = ref(3)
function update(num){
score.value = num
}
</script>组件的 v-model
对于自定义组件来说,v-model 是传递属性和接收组件事件两个写法的简写。
把属性名修改成 modelValue,然后如果我们想在前端页面进行点击评级的操作,只需要通过 update:modelValue 这个 emit 事件发出通知
javascript
let props = defineProps({
modelValue: Number,
theme:{type:String,default:'orange'}
})
let emits = defineEmits(['update:modelValue'])完整代码
Vue SFC
<template>
<div :style="fontstyle">
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
import {computed, defineEmits, defineProps, ref} from "vue";
let props = defineProps({
modelValue: Number,
theme: {type: String, default: 'orange'}
})
let emits = defineEmits(['update:modelValue'])
let width = ref(props.modelValue)
function onRate(num){
emits('update:modelValue',num)
}
function mouseOver(i) {
width.value = i
}
function mouseOut() {
width.value = props.modelValue
}
const fontwidth = computed(() => `width:${width.value}em;`)
const themeObj = {
'black': '#00',
'white': '#fff',
'red': '#f5222d',
'orange': '#fa541c',
'yellow': '#fadb14',
'green': '#73d13d',
'blue': '#40a9ff',
}
const fontstyle = computed(() => {
return `color:${themeObj[props.theme]};`
})
</script>
<style scoped>
.rate {
position: relative;
display: inline-block;
}
.rate > span.hollow {
position: absolute;
display: inline-block;
top: 0;
left: 0;
width: 0;
overflow: hidden;
}
</style>html
<template>
<h1>你的评分是 {{score}}</h1>
<Rate v-model="score"></Rate>
</template>插槽
在 Vue 中直接使用 slot 组件来显示组件的子元素,也就是所谓的插槽
Vue SFC
<template>
<div :style="fontstyle">
<slot></slot>
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @click="onRate(num)" @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>使用 Rate 组件的时候,组件的子元素都会放在评级组件之前。除了文本,也可以传递其他组件或者 html 标签
Vue SFC
<Rate v-model="score">课程评分</Rate>
<Rate v-model="score">
<img width="14" src="/favicon.ico">
</Rate>