可跨天选则次日的时间选择器

1、该组件可用在当不可选日期又需要跨天选择时间时的场景,交互效果类似elementUI的TimePicker 时间选择器

2、图例

效果展示

3、父组件代码

// html中使用
<TimePicker :id="'32'" :time="times" @selectTime="(time) => times = time" />

// 引入并注册组件
import TimePicker from "@/components/timePicker";

4、子组件代码


<template>
  <div class="timeInputBox" :id="'t1-' + id" :ref="'ref-' + id">
    <el-input ref="timeInput" :id="'t2-' + id" @focus="focus" v-model="input" :placeholder="placeholder" />
    <div class="menuBox" :class="{ menuBox: true, activemenuBox: show }" :id="'t3-' + id">
      <div class="triangle" :id="'t4-' + id"></div>
      <div class="menuMain" :id="'t5-' + id" @mouseenter="handleMouse('Enter')" @mouseleave="handleMouse('Leave')">
        <div class="leftTime container" :id="'t6-' + id" ref="leftTime" @mouseenter="handleMouse('Enter')"
          @mouseleave="handleMouse('Leave')">
          <div class="timeItem" :id="'t7-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
            :key="item + '-1'" />
          <div @click="changeTime('h', item, index)" :ref="'timeItem' + index" v-for="(item, index) in timeList.hour"
            :key="index + '01'" :class="{ activeTime: index == selectHourIndex }" :id="'t8-' + id">{{ textbuil(item) }}
          </div>
          <div class="timeItem" :id="'t9-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
            :key="item + '-2'" />
        </div>
        <div class="rightTime container" :id="'t10-' + id" ref="rightTime" @mouseenter="handleMouse('Enter')"
          @mouseleave="handleMouse('Leave')">
          <div class="timeItem" :id="'t11-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
            :key="item + '-3'" />
          <div :id="'t12-' + id" @click="changeTime('m', item, index)" v-for="(item, index) in timeList.minute"
            :key="index + '02'" :class="{ activeTime: index == selectMinuteIndex }">{{ item }}
          </div>
          <div class="timeItem" :id="'t13-' + id" :style="{ height: clientHeight + 'px' }" v-for="item in [1, 2]"
            :key="item + '-4'" />
        </div>
        <div class="botBut" :id="'t4-' + id">
          <el-button class="elbutton" :id="'t15-' + id" type="text" @click="selectTime('yes', '00:00')">确定</el-button>
          <el-button class="elbutton" :id="'t16-' + id" style="color: #333;" type="text"
            @click="selectTime('close')">取消</el-button>
        </div>
        <div class="line" :id="'t17-' + id" :style="{ top: `calc(50% - ${clientHeight - 5}px)` }"></div>
        <div class="line" :id="'t18-' + id" :style="{ top: `calc(50% + ${5}px)` }"></div>
      </div>
    </div>
  </div>
</template>

<script>
let inTimepickerPage = false
export default {
  name: "timePicker",
  components: {},
  props: {
    id: { // 多个时必传且不能相同
      type: String,
      default: Math.round(Math.random() * (99999999999999 - 10000000000000) + 10000000000000) + '',
    },
    time: { // 格式 10:30
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '请选择时间',
    },
    timeList: { // hour: 24时=>次日00时,25时=>次日01时,以此类推
      type: Object,
      default: () => {
        return {
          hour: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"],
          minute: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59"],
        }
      }
    }
  },
  data() {
    return {
      input: '',
      show: false,
      leftTimeScroll: 0,
      rightTimeScroll: 0,
      clientHeight: 36,
      selectHourIndex: -1,
      selectMinuteIndex: -1,
      setTimeing: false,
    };
  },
  created() {
    inTimepickerPage = true
  },
  mounted() {
    // 点击页面其他地方关闭时间选择组件
    document.addEventListener('click', (e) => {
      if (!inTimepickerPage) return
      const [sn, ids] = e.target.id?.split("-")
      if (this.id != ids) {
        this.selectTime('yes')
        this.show = false
      }
    })
    // 监听时/分滚动
    const leftTime = this.$refs.leftTime;
    const rightTime = this.$refs.rightTime;
    leftTime.addEventListener('scroll', () => {
      const scrollTop = this.$refs.leftTime.scrollTop
      if (!this.time && !this.input && scrollTop < 2) return
      this.selectHourIndex = Math.round(scrollTop / this.clientHeight); // 加粗选中数字
      this.input = this.textbuil(this.timeList.hour[this.selectHourIndex]) + ':' + this.timeList.minute[this.selectMinuteIndex]
    })
    rightTime.addEventListener('scroll', () => {
      const scrollTop = this.$refs.rightTime.scrollTop;
      if (!this.time && !this.input && scrollTop < 2) return
      this.selectMinuteIndex = Math.round(scrollTop / this.clientHeight); // 加粗选中数字
      this.input = this.textbuil(this.timeList.hour[this.selectHourIndex]) + ':' + this.timeList.minute[this.selectMinuteIndex]
    })
  },
  methods: {
    focus() {
      this.selectHourIndex = 0;
      this.selectMinuteIndex = 0;
      this.show = true
      this.$emit('focus')
    },
    changeTime(type, h, m) {
      if (type == 'h') {
        this.$refs.leftTime.scrollTop = Math.max(h * 1, 0) * this.clientHeight;
      } else {
        this.$refs.rightTime.scrollTop = m * this.clientHeight;
      }
    },
    handleMouse(type) {
      // 鼠标移入移出矫正时间对齐
      ['leftTime', 'rightTime'].map(item => {
        const scrollTop = this.$refs[item].scrollTop
        const selectIndex = Math.round(scrollTop / this.clientHeight)
        this.$refs[item].scrollTop = selectIndex * this.clientHeight;
      })
    },
    selectTime(type, times) {
      // 限制监听关闭窗口时自动赋值
      if (this.setTimeing) return
      this.setTimeing = true
      setTimeout(() => {
        this.setTimeing = false
      }, 100);

      if (type == 'yes') {
        const censend = this.selectHourIndex == -1 || this.selectMinuteIndex == -1 || (!this.time && !this.input)
        if (censend && times) return this.$emit('selectTime', times);
        if (censend) return
        const time = this.timeList.hour[this.selectHourIndex] + ':' + this.timeList.minute[this.selectMinuteIndex]
        this.$emit('selectTime', time);
      } else {
        const [leftDistance = 0, rightDistance = 0] = this.time?.split(':')
        if (!this.time && !this.input) return
        this.input = this.textbuil(leftDistance) + ':' + rightDistance
      }
      setTimeout(() => {
        this.$nextTick(() => {
          this.$refs.timeInput?.blur()
          this.show = false
        })
      }, 50);
    },
    textbuil(data) {
      if (data > 23) {
        data = data * 1
        return '次日0' + (data - 24)
      }
      return data
    },
  },
  watch: {
    time: {
      handler: function (newVal, oldVal) {
        console.log('time', newVal?.split(':'), newVal, oldVal);
        const [leftDistance, rightDistance] = newVal?.split(':')
        if (!newVal) return
        this.input = this.textbuil(leftDistance) + ':' + rightDistance
      },
      deep: true,
      immediate: true
    },
    show: {
      handler: function (newVal, oldVal) {
        if (newVal) {
          this.$nextTick(() => { // 一个字36px
            this.clientHeight = this.$refs.timeItem0?.[0].clientHeight || 36;
            const [leftDistance = 0, rightDistance = 0] = this.time?.split(':')
            this.$refs.leftTime.scrollTop = Math.max(leftDistance * 1, 0) * this.clientHeight + 1;
            this.$refs.rightTime.scrollTop = rightDistance * this.clientHeight + 1;
            setTimeout(() => { // 延迟再次改变scrollTop调用addEventListener('scroll')赋值
              this.$refs.leftTime.scrollTop = this.$refs.leftTime.scrollTop - 1
              this.$refs.rightTime.scrollTop = this.$refs.rightTime.scrollTop - 1
            }, 10);
          })
        } else {
          document.removeEventListener('scroll', () => { })
          document.removeEventListener('click', () => { })
        }
      }
    },
  },
  destroyed() {
    inTimepickerPage = false
    document.removeEventListener('scroll', () => { })
    document.removeEventListener('click', () => { })
  },
};
</script>
<style lang="scss" scoped>
.timeInputBox {
  position: relative;

  .menuBox {
    position: absolute;
    width: 182px;
    height: 230px;
    z-index: 9;
    transition: .25s;
    overflow: hidden;
    transform: scaleY(0);
    transform-origin: top;
    box-shadow: 10px 9px 20px -10px #e1dfdf;

    .triangle {
      width: 0;
      height: 0;
      border-top: 6px solid transparent;
      border-bottom: 6px solid #fff;
      border-left: 6px solid transparent;
      border-right: 6px solid transparent;
      margin-left: 10px;
      margin-bottom: -1px;
      z-index: 10;
      position: absolute;
      top: 0;
      left: 10px;
    }

    .menuMain {
      background-color: #fff;
      height: calc(100% - 10px);
      width: 100%;
      border-radius: 4px;
      border: 1px solid #eaeaea;
      z-index: 9;
      margin-top: 10px;
      box-shadow: 2px 1px 20px -11px #333;

      .container {
        float: left;
        height: calc(100% - 36px);
        width: 50%;
        overflow: auto;
        text-align: center;
        font-size: 12px;
        color: #606266;
        cursor: pointer;

        :hover {
          background-color: #4d7fff16;
        }

        .timeItem:hover {
          background-color: transparent;
        }

        .activeTime {
          font-weight: bold;
          color: #000;

          &:hover {
            background-color: transparent;
          }
        }
      }

      .botBut {
        width: 99%;
        height: 36px;
        border-top: 1px solid #d7d7d7;
        position: absolute;
        bottom: 2px;
        background-color: #fff;

        .elbutton {
          font-size: 12px;
          float: right;
          margin: auto 10px;
          font-weight: 500;
        }

      }

      .line {
        position: absolute;
        height: 1px;
        width: calc((100% - 40px));
        left: 20px;
        background-color: #e1e1e1;
        // border-top: 1px solid #e1e1e1;
        // border-bottom: 1px solid #e1e1e1;
      }
    }
  }

  .activemenuBox {
    transform: scaleY(1);
  }
}

.container::-webkit-scrollbar {
  width: 7px;
  // height: 5px;
}

.container::-webkit-scrollbar-thumb {
  // background: linear-gradient(to bottom right, #4d7fff 0%, #1a56ff 100%);
  background-color: #dfdfdf;
  border-radius: 5px;
}

.container::-webkit-scrollbar-track {
  background-color: #fafafa;
  // border: 1px solid #ccc;
}

.container::-webkit-scrollbar-button {
  background-color: #fff;
  border-radius: 5px;
}

.container::-webkit-scrollbar-button:hover {
  background-color: #c1c1c1;
}
</style>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值