Rate組件源碼比較簡單,有添加部分注釋
main.vue
<template>
<!--valuenow當前的評分 valuetext當前顯示的文本-->
<div
class="el-rate"
@keydown="handleKey"
role="slider"
:aria-valuenow="currentValue"
:aria-valuetext="text"
aria-valuemin="0"
:aria-valuemax="max"
tabindex="0">
<!--包裹每個星的標簽-->
<span
v-for="(item, key) in max"
class="el-rate__item"
@mousemove="setCurrentValue(item, $event)"
@mouseleave="resetCurrentValue"
@click="selectValue(item)"
:style="{ cursor: rateDisabled ? 'auto' : 'pointer' }"
:key="key">
<!--顯示評星的標簽-->
<i :class="[classes[item - 1], { 'hover': hoverIndex === item }]"
class="el-rate__icon"
:style="getIconStyle(item)">
<!--這里主要是當評分出現小數,顯示左邊高亮的半星-->
<i v-if="showDecimalIcon(item)"
:class="decimalIconClass"
:style="decimalStyle"
class="el-rate__decimal">
</i>
</i>
</span>
<!--showText是否顯示輔助文字,若為真,則會從 texts 數組中選取當前分數對應的文字內容;showScore是否顯示當前分數,show-score 和 show-text 不能同時為真-->
<span v-if="showText || showScore" class="el-rate__text" :style="{ color: textColor }">{{ text }}</span>
</div>
</template>
<script>
import { hasClass } from 'element-ui/src/utils/dom';
import Migrating from 'element-ui/src/mixins/migrating';
export default {
name: 'ElRate',
mixins: [Migrating],
//provider/inject:簡單的來說就是在父組件中通過provider來提供變量,然后在子組件中通過inject來注入變量。
inject: {
elForm: {
default: ''
}
},
data() {
return {
pointerAtLeftHalf: true,
currentValue: this.value,
hoverIndex: -1
};
},
props: {
value: {
type: Number,
default: 0
},
lowThreshold: { //低分和中等分數的界限值,值本身被划分在低分中
type: Number,
default: 2
},
highThreshold: { //高分和中等分數的界限值,值本身被划分在高分中
type: Number,
default: 4
},
max: { //最大分值
type: Number,
default: 5
},
colors: { //icon 的顏色數組,共有 3 個元素,為 3 個分段所對應的顏色
type: Array,
default() {
return ['#F7BA2A', '#F7BA2A', '#F7BA2A'];
}
},
voidColor: { //未選中 icon 的顏色
type: String,
default: '#C6D1DE'
},
disabledVoidColor: { //只讀時未選中 icon 的顏色
type: String,
default: '#EFF2F7'
},
iconClasses: { //icon 的類名數組,共有 3 個元素,為 3 個分段所對應的類名
type: Array,
default() {
return ['el-icon-star-on', 'el-icon-star-on', 'el-icon-star-on'];
}
},
voidIconClass: { //未選中 icon 的類名
type: String,
default: 'el-icon-star-off'
},
disabledVoidIconClass: { //只讀時未選中 icon 的類名
type: String,
default: 'el-icon-star-on'
},
disabled: { //是否為只讀
type: Boolean,
default: false
},
allowHalf: { //是否允許半選
type: Boolean,
default: false
},
showText: { //是否顯示輔助文字,若為真,則會從 texts 數組中選取當前分數對應的文字內容
type: Boolean,
default: false
},
showScore: { //是否顯示當前分數,show-score 和 show-text 不能同時為真
type: Boolean,
default: false
},
textColor: { //輔助文字的顏色
type: String,
default: '#1f2d3d'
},
texts: { //輔助文字數組
type: Array,
default() {
return ['極差', '失望', '一般', '滿意', '驚喜'];
}
},
scoreTemplate: { //分數顯示模板
type: String,
default: '{value}'
}
},
computed: {
text() {
let result = '';
//如果顯示當前分數
if (this.showScore) {
//如果當前是只讀狀態,就顯示v-model綁定的值,否則根據用戶的評分顯示值
result = this.scoreTemplate.replace(/\{\s*value\s*\}/, this.rateDisabled
? this.value
: this.currentValue);
} else if (this.showText) { //如果顯示輔助文字,則根據用戶設置評分currentValue來顯示texts數組中的文字
result = this.texts[Math.ceil(this.currentValue) - 1];
}
return result;
},
//高亮半星時添加的樣式,顏色以及寬度
decimalStyle() {
let width = '';
if (this.rateDisabled) {
//這里判斷value的值是否含有小數,有小數的則這里寬度為50%,為整數的話為0%
width = `${ this.valueDecimal < 50 ? 0 : 50 }%`;
}
if (this.allowHalf) {
width = '50%';
}
return {
color: this.activeColor,
width
};
},
valueDecimal() {
return this.value * 100 - Math.floor(this.value) * 100;
},
decimalIconClass() {
return this.getValueFromMap(this.value, this.classMap);
},
voidClass() {
return this.rateDisabled ? this.classMap.disabledVoidClass : this.classMap.voidClass;
},
//根據currentValue的分所在的等級,返回對應的類名
activeClass() {
return this.getValueFromMap(this.currentValue, this.classMap);
},
colorMap() {
return {
lowColor: this.colors[0],
mediumColor: this.colors[1],
highColor: this.colors[2],
voidColor: this.voidColor,
disabledVoidColor: this.disabledVoidColor
};
},
//根據currentValue的分所在的等級,返回對應的顏色
activeColor() {
return this.getValueFromMap(this.currentValue, this.colorMap);
},
//這里主要是判斷該星是選中還是未選中,分別加入選中和未選中icon類名
classes() {
let result = [];
let i = 0;
let threshold = this.currentValue;
if (this.allowHalf && this.currentValue !== Math.floor(this.currentValue)) {
threshold--;
}
for (; i < threshold; i++) {
result.push(this.activeClass);
}
for (; i < this.max; i++) {
result.push(this.voidClass);
}
return result;
},
classMap() {
return {
lowClass: this.iconClasses[0],
mediumClass: this.iconClasses[1],
highClass: this.iconClasses[2],
voidClass: this.voidIconClass,
disabledVoidClass: this.disabledVoidIconClass
};
},
rateDisabled() {
//是否為只讀,或者父組件el-form中disabled的屬性值,disabled是否禁用該表單內的所有組件。若設置為 true,則表單內組件上的 disabled 屬性不再生效
return this.disabled || (this.elForm || {}).disabled;
}
},
watch: {
value(val) {
this.currentValue = val;
this.pointerAtLeftHalf = this.value !== Math.floor(this.value);
}
},
methods: {
getMigratingConfig() {
return {
props: {
'text-template': 'text-template is renamed to score-template.'
}
};
},
//判斷當前value在屬於低分、中等分、高分中的哪個,根據不同等級返回不同的類名或者顏色
getValueFromMap(value, map) {
let result = '';
if (value <= this.lowThreshold) {
result = map.lowColor || map.lowClass;
} else if (value >= this.highThreshold) {
result = map.highColor || map.highClass;
} else {
result = map.mediumColor || map.mediumClass;
}
return result;
},
showDecimalIcon(item) {
//如果當前value包含小數,並且item - 1 < this.value <item, showWhenDisabled為true
let showWhenDisabled = this.rateDisabled && this.valueDecimal > 0 && item - 1 < this.value && item > this.value;
//這里主要也是判斷是否當前星是否應顯示半星
let showWhenAllowHalf = this.allowHalf &&
this.pointerAtLeftHalf &&
item - 0.5 <= this.currentValue &&
item > this.currentValue;
return showWhenDisabled || showWhenAllowHalf;
},
//返回當前星圖標的顏色
getIconStyle(item) {
//voidColor的值是根據是否只讀來判斷返回disabled-void-color或者void-color
const voidColor = this.rateDisabled ? this.colorMap.disabledVoidColor : this.colorMap.voidColor;
return {
//判斷當前星是顯示高亮的顏色還是未選中時的顏色
color: item <= this.currentValue ? this.activeColor : voidColor
};
},
//點擊時設置值
selectValue(value) {
if (this.rateDisabled) {
return;
}
//當可以顯示半星時,這塊傳遞的值為currentValue(鼠標移上去時會計算是否超過一半)
if (this.allowHalf && this.pointerAtLeftHalf) {
this.$emit('input', this.currentValue);
this.$emit('change', this.currentValue);
} else { //當不顯示半星直接返回value
this.$emit('input', value);
this.$emit('change', value);
}
},
//當按鍵按下時所調用的方法
handleKey(e) {
//如果組件被禁用則按鍵事件無效
if (this.rateDisabled) {
return;
}
let currentValue = this.currentValue;
const keyCode = e.keyCode;
//當按上下左右鍵的時候,允許半選則在currentValue加或減0.5,不允許則加或減1
if (keyCode === 38 || keyCode === 39) { // up / right
if (this.allowHalf) {
currentValue += 0.5;
} else {
currentValue += 1;
}
e.stopPropagation();
e.preventDefault();
} else if (keyCode === 37 || keyCode === 40) { // left /down
if (this.allowHalf) {
currentValue -= 0.5;
} else {
currentValue -= 1;
}
e.stopPropagation();
e.preventDefault();
}
currentValue = currentValue < 0 ? 0 : currentValue;
currentValue = currentValue > this.max ? this.max : currentValue;
//將currentValue通過input傳遞給子組件綁定的v-model值,觸發change事件,子組件可以change在獲取改變后的值
this.$emit('input', currentValue);
this.$emit('change', currentValue);
},
//鼠標移動時改變評星的值
setCurrentValue(value, event) {
if (this.rateDisabled) {
return;
}
/* istanbul ignore if */
if (this.allowHalf) {
let target = event.target;
//鼠標移動到包裹星星圖標的span標簽時,獲取到顯示星的icon標簽
if (hasClass(target, 'el-rate__item')) {
target = target.querySelector('.el-rate__icon');
}
if (hasClass(target, 'el-rate__decimal')) {
target = target.parentNode;
}
//根據鼠標移到一顆星的左邊一半以內的位置,則減去當前值的0.5,否則就是等於當前值
this.pointerAtLeftHalf = event.offsetX * 2 <= target.clientWidth;
this.currentValue = this.pointerAtLeftHalf ? value - 0.5 : value;
} else {
//不允許半選時,鼠標移到那顆星就等於當前的值
this.currentValue = value;
}
//記錄鼠標移動的位置
this.hoverIndex = value;
},
//鼠標移出時設置當前的值
resetCurrentValue() {
if (this.rateDisabled) {
return;
}
if (this.allowHalf) {
//如果當前的value是小數,pointerAtLeftHalf為true,如果是整數則為false,這里主要是為了點擊時用來判斷傳哪個值
this.pointerAtLeftHalf = this.value !== Math.floor(this.value);
}
//鼠標移上去時currentValue會改變,移走時currentValue等於之前的value
this.currentValue = this.value;
this.hoverIndex = -1;
}
},
created() {
if (!this.value) {
this.$emit('input', 0);
}
}
};
</script>