time-select.vue 4.76 KB
<template>
  <transition name="el-zoom-in-top" @before-enter="handleMenuEnter" @after-leave="$emit('dodestroy')">
    <div
      ref="popper"
      v-show="visible"
      :style="{ width: width + 'px' }"
      :class="popperClass"
      class="el-picker-panel time-select el-popper">
      <el-scrollbar noresize wrap-class="el-picker-panel__content">
        <div class="time-select-item"
          v-for="item in items"
          :class="{ selected: value === item.value, disabled: item.disabled, default: item.value === defaultValue }"
          :disabled="item.disabled"
          :key="item.value"
          @click="handleClick(item)">{{ item.value }}</div>
      </el-scrollbar>
    </div>
  </transition>
</template>

<script type="text/babel">
  import ElScrollbar from 'element-ui/packages/scrollbar';
  import scrollIntoView from 'element-ui/src/utils/scroll-into-view';

  const parseTime = function(time) {
    const values = (time || '').split(':');
    if (values.length >= 2) {
      const hours = parseInt(values[0], 10);
      const minutes = parseInt(values[1], 10);

      return {
        hours,
        minutes
      };
    }
    /* istanbul ignore next */
    return null;
  };

  const compareTime = function(time1, time2) {
    const value1 = parseTime(time1);
    const value2 = parseTime(time2);

    const minutes1 = value1.minutes + value1.hours * 60;
    const minutes2 = value2.minutes + value2.hours * 60;

    if (minutes1 === minutes2) {
      return 0;
    }

    return minutes1 > minutes2 ? 1 : -1;
  };

  const formatTime = function(time) {
    return (time.hours < 10 ? '0' + time.hours : time.hours) + ':' + (time.minutes < 10 ? '0' + time.minutes : time.minutes);
  };

  const nextTime = function(time, step) {
    const timeValue = parseTime(time);
    const stepValue = parseTime(step);

    const next = {
      hours: timeValue.hours,
      minutes: timeValue.minutes
    };

    next.minutes += stepValue.minutes;
    next.hours += stepValue.hours;

    next.hours += Math.floor(next.minutes / 60);
    next.minutes = next.minutes % 60;

    return formatTime(next);
  };

  export default {
    components: { ElScrollbar },

    watch: {
      value(val) {
        if (!val) return;
        this.$nextTick(() => this.scrollToOption());
      }
    },

    methods: {
      handleClick(item) {
        if (!item.disabled) {
          this.$emit('pick', item.value);
        }
      },

      handleClear() {
        this.$emit('pick', null);
      },

      scrollToOption(selector = '.selected') {
        const menu = this.$refs.popper.querySelector('.el-picker-panel__content');
        scrollIntoView(menu, menu.querySelector(selector));
      },

      handleMenuEnter() {
        const selected = this.items.map(item => item.value).indexOf(this.value) !== -1;
        const hasDefault = this.items.map(item => item.value).indexOf(this.defaultValue) !== -1;
        const option = (selected && '.selected') || (hasDefault && '.default') || '.time-select-item:not(.disabled)';
        this.$nextTick(() => this.scrollToOption(option));
      },

      scrollDown(step) {
        const items = this.items;
        const length = items.length;
        let total = items.length;
        let index = items.map(item => item.value).indexOf(this.value);
        while (total--) {
          index = (index + step + length) % length;
          if (!items[index].disabled) {
            this.$emit('pick', items[index].value, true);
            return;
          }
        }
      },

      isValidValue(date) {
        return this.items.filter(item => !item.disabled).map(item => item.value).indexOf(date) !== -1;
      },

      handleKeydown(event) {
        const keyCode = event.keyCode;
        if (keyCode === 38 || keyCode === 40) {
          const mapping = { 40: 1, 38: -1 };
          const offset = mapping[keyCode.toString()];
          this.scrollDown(offset);
          event.stopPropagation();
          return;
        }
      }
    },

    data() {
      return {
        popperClass: '',
        start: '09:00',
        end: '18:00',
        step: '00:30',
        value: '',
        defaultValue: '',
        visible: false,
        minTime: '',
        maxTime: '',
        width: 0
      };
    },

    computed: {
      items() {
        const start = this.start;
        const end = this.end;
        const step = this.step;

        const result = [];

        if (start && end && step) {
          let current = start;
          while (compareTime(current, end) <= 0) {
            result.push({
              value: current,
              disabled: compareTime(current, this.minTime || '-1:-1') <= 0 ||
                compareTime(current, this.maxTime || '100:100') >= 0
            });
            current = nextTime(current, step);
          }
        }

        return result;
      }
    }
  };
</script>