TimeRangePicker.vue 5.93 KB
<script setup lang="ts">
  import { TimePicker } from 'ant-design-vue';
  import { Moment } from 'moment';
  import { computed, unref } from 'vue';
  import { dateUtil } from '/@/utils/dateUtil';

  interface TimeRangePickerProps {
    timestampToNumber?: boolean;
    value?: Record<string, number>;
    startValue?: number;
    endValue?: number;
    startField?: string;
    endField?: string;
    disabled?: boolean;
  }

  enum TimePickerTypeEnum {
    START = 'START',
    END = 'END',
  }

  const emit = defineEmits(['update:value', 'update:startValue', 'update:endValue', 'change']);

  const props = withDefaults(defineProps<TimeRangePickerProps>(), {
    value: () => ({}),
    startField: 'startsOn',
    endField: 'endsOn',
  });

  const getTimeMoment = (value: number) => {
    value = value / 1000;
    const hour = Math.floor(value / 60 / 60);
    const minute = Math.floor((value - hour * 60 * 60) / 60);
    const second = value - hour * 60 * 60 - minute * 60;

    return dateUtil({ hour, minute, second });
  };

  const getRangeValue = computed(() => {
    const { value, startValue, endValue, startField, endField } = props;

    let startTs = startValue || value[startField];

    let endTs = endValue || value[endField];

    return {
      startTs: startTs ? getTimeMoment(startTs) : null,
      endTs: endTs ? getTimeMoment(endTs) : null,
    };
  });

  const getTimestamp = (moment: Moment | null) => {
    if (!moment) return null;
    const { hours, minutes, seconds } = moment.toObject();
    return (hours * 60 * 60 + minutes * 60 + seconds) * 1000;
  };

  const handleTimeChange = (time: Moment, type: TimePickerTypeEnum) => {
    const { value, startField, endField } = props;
    const { startTs, endTs } = unref(getRangeValue);

    const rangeValue = {
      [startField]: getTimestamp(startTs),
      [endField]: getTimestamp(endTs),
    };

    Reflect.set(
      rangeValue,
      type === TimePickerTypeEnum.START ? startField : endField,
      getTimestamp(time)
    );

    if (
      rangeValue[startField] &&
      rangeValue[endField] &&
      rangeValue[startField]! > rangeValue[endField]!
    ) {
      Reflect.set(rangeValue, type === TimePickerTypeEnum.START ? startField : endField, null);
    }

    emit('update:startValue', rangeValue[startField]);
    emit('update:endValue', rangeValue[endField]);
    emit('update:value', rangeValue);
    emit('change', value);
  };

  const handleBasicDisabledHours = (type: TimePickerTypeEnum) => {
    return () => {
      const { startTs, endTs } = unref(getRangeValue);

      if (type === TimePickerTypeEnum.START && !endTs) return [];
      if (type === TimePickerTypeEnum.END && !startTs) return [];

      const selectedHour = type === TimePickerTypeEnum.END ? startTs!.hour() : endTs!.hour();

      return type === TimePickerTypeEnum.START
        ? Array.from({ length: 24 - selectedHour }, (_, index) => selectedHour + index + 1)
        : Array.from({ length: selectedHour }, (_, index) => index);
    };
  };

  const handleBasicDisabledMinutes = (type: TimePickerTypeEnum) => {
    return (currentSelectedHour: number) => {
      const { startTs, endTs } = unref(getRangeValue);

      if (type === TimePickerTypeEnum.START && !endTs) return [];
      if (type === TimePickerTypeEnum.END && !startTs) return [];

      const selectedTime = type === TimePickerTypeEnum.END ? startTs : endTs;
      const selectedHour = selectedTime!.hour();
      const selectedMinute = selectedTime!.minute();

      if (type == TimePickerTypeEnum.START) {
        return selectedHour > currentSelectedHour
          ? []
          : Array.from({ length: 60 - selectedMinute }, (_, index) => selectedMinute + index + 1);
      } else {
        return selectedHour < currentSelectedHour
          ? []
          : Array.from({ length: selectedMinute }, (_, index) => index);
      }
    };
  };

  const handleBasicDisabledSeconds = (type: TimePickerTypeEnum) => {
    return (currentSelectedHour: number, currentSelectedMinute: number) => {
      const { startTs, endTs } = unref(getRangeValue);

      if (type === TimePickerTypeEnum.START && !endTs) return [];
      if (type === TimePickerTypeEnum.END && !startTs) return [];

      const selectedTime = type === TimePickerTypeEnum.END ? startTs : endTs;
      const selectedHour = selectedTime!.hour();
      const selectedMinute = selectedTime!.minute();
      const selectedSeconds = selectedTime!.seconds();

      if (type === TimePickerTypeEnum.START) {
        return selectedHour >= currentSelectedHour && selectedMinute > currentSelectedMinute
          ? []
          : Array.from({ length: 60 - selectedSeconds }, (_, index) => selectedSeconds + index + 1);
      } else {
        return selectedHour <= currentSelectedHour && selectedMinute < currentSelectedMinute
          ? []
          : Array.from({ length: selectedSeconds }, (_, index) => index);
      }
    };
  };
</script>

<template>
  <main class="time-range-picker flex items-center">
    <TimePicker
      :value="getRangeValue.startTs"
      format="HH:mm"
      :disabled="disabled"
      @change="(time) => handleTimeChange(time, TimePickerTypeEnum.START)"
      :disabled-hours="handleBasicDisabledHours(TimePickerTypeEnum.START)"
      :disabled-minutes="handleBasicDisabledMinutes(TimePickerTypeEnum.START)"
      :disabled-seconds="handleBasicDisabledSeconds(TimePickerTypeEnum.START)"
    />
    <slot v-if="$slots?.separator" name="separator"></slot>
    <span v-else class="mx-4">~</span>
    <TimePicker
      :value="getRangeValue.endTs"
      format="HH:mm"
      :disabled="disabled"
      @change="(time) => handleTimeChange(time, TimePickerTypeEnum.END)"
      :disabled-hours="handleBasicDisabledHours(TimePickerTypeEnum.END)"
      :disabled-minutes="handleBasicDisabledMinutes(TimePickerTypeEnum.END)"
      :disabled-seconds="handleBasicDisabledSeconds(TimePickerTypeEnum.END)"
    />
  </main>
</template>

<style scoped lang="less">
  .time-range-picker {
    :deep(.ant-time-picker) {
      width: auto;
    }
  }
</style>