element rate源码

<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>

 

posted on 2019-06-05 14:19  心痛随缘  阅读(1372)  评论(0编辑  收藏  举报