<template> <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> <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 { isObject } from 'element-ui/src/utils/types'; import Migrating from 'element-ui/src/mixins/migrating'; export default { name: 'ElRate', mixins: [Migrating], inject: { elForm: { default: '' } }, data() { return { // 鼠標在目標元素一半的左側 pointerAtLeftHalf: true, // 當前值 currentValue: this.value, hoverIndex: -1 }; }, props: { // 綁定值 number — 0 value: { type: Number, default: 0 }, // 低分和中等分數的界限值,值本身被划分在低分中 number — 2 lowThreshold: { type: Number, default: 2 }, // 高分和中等分數的界限值,值本身被划分在高分中 highThreshold: { type: Number, default: 4 }, // 最大分值 number — 5 max: { type: Number, default: 5 }, // icon 的顏色。若傳入數組,共有 3 個元素,為 3 個分段所對應的顏色;若傳入對象,可自定義分段,鍵名為分段的界限值, // 鍵值為對應的顏色 array/object — ['#F7BA2A', '#F7BA2A', '#F7BA2A'] colors: { type: [Array, Object], default() { return ['#F7BA2A', '#F7BA2A', '#F7BA2A']; } }, // 未選中 icon 的顏色 string — #C6D1DE voidColor: { type: String, default: '#C6D1DE' }, // 只讀時未選中 icon 的顏色 string — #EFF2F7 disabledVoidColor: { type: String, default: '#EFF2F7' }, // icon 的類名。若傳入數組,共有 3 個元素,為 3 個分段所對應的類名;若傳入對象,可自定義分段,鍵名為分段的界限值,鍵值為對應的類名 // array/object — ['el-icon-star-on', 'el-icon-star-on','el-icon-star-on'] iconClasses: { type: [Array, Object], default() { return ['el-icon-star-on', 'el-icon-star-on', 'el-icon-star-on']; } }, // 未選中 icon 的類名 string — el-icon-star-off voidIconClass: { type: String, default: 'el-icon-star-off' }, // 只讀時未選中 icon 的類名 string — el-icon-star-on disabledVoidIconClass: { type: String, default: 'el-icon-star-on' }, // 是否為只讀 disabled: { type: Boolean, default: false }, // 是否允許半選 allowHalf: { type: Boolean, default: false }, // 是否顯示輔助文字,若為真,則會從 texts 數組中選取當前分數對應的文字內容 showText: { type: Boolean, default: false }, // 是否顯示當前分數,show-score 和 show-text 不能同時為真 showScore: { type: Boolean, default: false }, // 輔助文字的顏色 textColor: { type: String, default: '#1f2d3d' }, // 輔助文字數組 array — ['極差', '失望', '一般', '滿意', '驚喜'] texts: { type: Array, default() { return ['極差', '失望', '一般', '滿意', '驚喜']; } }, // 分數顯示模板 scoreTemplate: { type: String, default: '{value}' } }, computed: { // 文本 text() { let result = ''; if (this.showScore) { result = this.scoreTemplate.replace(/\{\s*value\s*\}/, this.rateDisabled ? this.value : this.currentValue); } else if (this.showText) { result = this.texts[Math.ceil(this.currentValue) - 1]; } return result; }, // 樣式 decimalStyle() { let width = ''; if (this.rateDisabled) { // 差值 width = `${ this.valueDecimal }%`; } else if (this.allowHalf) { // 半星 width = '50%'; } return { color: this.activeColor, width }; }, // 差值 valueDecimal() { return this.value * 100 - Math.floor(this.value) * 100; }, // 字典 classMap() { return Array.isArray(this.iconClasses) ? { [this.lowThreshold]: this.iconClasses[0], [this.highThreshold]: { value: this.iconClasses[1], excluded: true }, [this.max]: this.iconClasses[2] } : this.iconClasses; }, // 根據值獲取類名 decimalIconClass() { return this.getValueFromMap(this.value, this.classMap); }, // 未選中的類名 voidClass() { return this.rateDisabled ? this.disabledVoidIconClass : this.voidIconClass; }, // 獲取當前值的類名 activeClass() { return this.getValueFromMap(this.currentValue, this.classMap); }, // 顏色字典 colorMap() { return Array.isArray(this.colors) ? { [this.lowThreshold]: this.colors[0], [this.highThreshold]: { value: this.colors[1], excluded: true }, [this.max]: this.colors[2] } : this.colors; }, // 當前的顏色 activeColor() { return this.getValueFromMap(this.currentValue, this.colorMap); }, 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; }, // 是否只讀 rateDisabled() { return this.disabled || (this.elForm || {}).disabled; } }, watch: { // 監聽value變化賦值給currentValue 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.' } }; }, // 根據字典獲取類名 getValueFromMap(value, map) { const matchedKeys = Object.keys(map) .filter(key => { const val = map[key]; const excluded = isObject(val) ? val.excluded : false; return excluded ? value < key : value <= key; }) .sort((a, b) => a - b); const matchedValue = map[matchedKeys[0]]; return isObject(matchedValue) ? matchedValue.value : (matchedValue || ''); }, // 顯示icon showDecimalIcon(item) { // 禁用是否顯示 let showWhenDisabled = this.rateDisabled && this.valueDecimal > 0 && item - 1 < this.value && item > this.value; /* istanbul ignore next */ let showWhenAllowHalf = this.allowHalf && this.pointerAtLeftHalf && item - 0.5 <= this.currentValue && item > this.currentValue; return showWhenDisabled || showWhenAllowHalf; }, // 獲取每個item的顏色 getIconStyle(item) { const voidColor = this.rateDisabled ? this.disabledVoidColor : this.voidColor; return { color: item <= this.currentValue ? this.activeColor : voidColor }; }, // 設置值 selectValue(value) { if (this.rateDisabled) { return; } if (this.allowHalf && this.pointerAtLeftHalf) { this.$emit('input', this.currentValue); this.$emit('change', this.currentValue); } else { this.$emit('input', value); this.$emit('change', value); } }, // 鍵盤事件 handleKey(e) { // 只讀返回 if (this.rateDisabled) { return; } let currentValue = this.currentValue; const keyCode = e.keyCode; // 上箭頭和右箭頭 if (keyCode === 38 || keyCode === 39) { // 允許半星增加0.5 if (this.allowHalf) { currentValue += 0.5; } else { // 否則增加1 currentValue += 1; } e.stopPropagation(); e.preventDefault(); // 左箭頭和下箭頭,同理 } else if (keyCode === 37 || keyCode === 40) { 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; // 向外拋出chang和input事件,並傳當前值 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; if (hasClass(target, 'el-rate__item')) { target = target.querySelector('.el-rate__icon'); } if (hasClass(target, 'el-rate__decimal')) { target = target.parentNode; } // 鼠標在左半星減去半星,否則不變 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) { this.pointerAtLeftHalf = this.value !== Math.floor(this.value); } this.currentValue = this.value; this.hoverIndex = -1; } }, // 初始化 created() { if (!this.value) { this.$emit('input', 0); } } }; </script>