时间联级组件

一、需求讲解

​ 因公司需求,要做一个时间联级组件(时分秒),项目用的ant-design-vue,这个UI框架里没有联级的,为了快速完成任务直接用了elementUI的el-time-picker,但又有其他问题:在添加上is-range后,selectableRange不生效,在网上找了好多讨论都说是不生效,最后也没找到解决方法。

​ 又尝试了iview的联级,它可以限制时间选择,但是样式和项目完全不匹配(它底层是一个input,无法拆开调整间距什么的样式),pass了。

​ 后来又改了需求:限制的时间直接不显示(ui框架的都是禁用),故所有ui框架都用不成了,自己手动封装吧…

二、直接上代码:

<template>
  <div class="timePiker_box">
    <div class="timePiker" :class="{ 'readonly': disabled }" @click="OpenSelect">
      <i class="el-icon-time"></i>
      <input class="timePiker_input" readonly type="text" v-model="startTime" autocomplete="off" placeholder="开始时间" :style="{cursor: disabled ? 'not-allowed' : 'pointer'}">
      <i class="timePiker_icon">~</i>
      <input class="timePiker_input" readonly type="text" v-model="endTime" autocomplete="off" placeholder="结束时间" :style="{cursor: disabled ? 'not-allowed' : 'pointer' }">
    </div>
    <div class="timePiker_select" v-show="visible">
      <div class="timePiker_select_start">
        <div class="timePiker_select_title">开始时间</div>
        <div class="selectBox">
          <div class="timePiker_list timePiker_list_start_time">
            <ul class="timePiker_select_start_time_ul">
              <li @click="selectItem('timePiker_list_start_time', index, item.disabled)" :class="{active: item.active, disabled: item.disabled }" class="timePiker_select_start_li" :style="{ display: item.disabled ? 'none' : 'block' }" v-for="(item,index) in startTimeList" :key="index">{{ item.i }}</li>
            </ul>
          </div>
          <div class="timePiker_list timePiker_list_start_minute">
            <ul class="timePiker_select_start_minute_ul">
              <li @click="selectItem('timePiker_list_start_minute', index, item.disabled)" :class="{ active: item.active, disabled: item.disabled }" class="timePiker_select_start_li" v-for="(item,index) in startMinuteList" :key="index">{{ item.i }}</li>
            </ul>
          </div>
        </div>
      </div>
      <div class="timePiker_select_end">
        <div class="timePiker_select_title">结束时间</div>
        <div class="selectBox">
          <div class="timePiker_list timePiker_list_end_time">
            <ul class="timePiker_select_end_time_ul">
              <li @click="selectItem('timePiker_list_end_time', index, item.disabled)" :class="{ active: item.active, disabled: item.disabled }" class="timePiker_select_end_li" :style="{ display: item.disabled ? 'none' : 'block' }" v-for="(item,index) in endTimeList" :key="index" >{{ item.i }}</li>
            </ul>
          </div>
          <div class="timePiker_list timePiker_list_end_minute">
            <ul class="timePiker_select_end_minute_ul">
              <li @click="selectItem('timePiker_list_end_minute', index, item.disabled)" :class="{ active: item.active, disabled: item.disabled }" class="timePiker_select_end_li" v-for="(item,index) in endMinuteList" :key="index">{{ item.i }}</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      startTime: '',
      endTime: '',
      visible: false,
      startTimeList: [],
      endTimeList: [],
      startMinuteList: [],
      endMinuteList: [],
    };
  },
  props: {
    start: {
      type: String,
      default: '08:00'
    },
    end: {
      type: String,
      default: '20:00'
    },
    bool: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },

  mounted(){
    window.addEventListener('mousedown', this.fn);
  },
  beforeDestroy () {
    window.removeEventListener('mousedown', this.fn);
  },
  watch: {
    start: {
      handler (val) {
        this.startTime = val
      },
      immediate: true
    },
    end: {
      handler (val) {
        this.endTime = val
      },
      immediate: true
    },
    bool: {
      handler (val) {
        this.startTimeList = Array.from({ length: 24 }, (v, k) => {
          return {
            'i': k > 9 ? k : '0' + k,
            'disabled': this.bool && [0, 1, 2, 3, 4, 5, 6, 7, 21, 22, 23].includes(k),
            'active': +this.startTime.slice(0, 2) == k
          }
        })
        this.startMinuteList = Array.from({ length: 60 }, (v, k) => {
          return {
            'i': k > 9 ? k : '0' + k,
            'disabled': false,
            'active': +this.startTime.slice(3, 5) == k
          }
        })
        this.endTimeList = Array.from({ length: 24 }, (v, k) => {
          return {
            'i': k > 9 ? k : '0' + k,
            'disabled': this.bool && [0, 1, 2, 3, 4, 5, 6, 7, 21, 22, 23].includes(k),
            'active': +this.endTime.slice(0, 2) == k
          }
        })
        this.endMinuteList = Array.from({ length: 60 }, (v, k) => {
          return {
            'i': k > 9 ? k : '0' + k,
            'disabled': false,
            'active': +this.endTime.slice(3, 5) == k
          }
        })
      },
      immediate: true
    },
    startTime: {
      handler (val) {
        let lastEnd = this.endTime
        if (val.slice(0, 2) > this.endTime.slice(0, 2) || (val.slice(0, 2) == this.endTime.slice(0, 2) && val.slice(3, 5) >= this.endTime.slice(3, 5))) {
          this.endTime = val
          // 时
          let findIndex = this.startTimeList.findIndex(ele => !ele.disabled)
          let time = +this.endTime.slice(0, 2) - findIndex
          this.scrollToPosition('timePiker_list_end_time', 36 * time)
          this.endTimeList[+lastEnd.slice(0, 2)].active = false
          this.endTimeList[+this.endTime.slice(0, 2)].active = true
          // 分
          this.scrollToPosition('timePiker_list_end_minute', +this.endTime.slice(3, 5) * 36)
          this.endMinuteList[+lastEnd.slice(3, 5)].active = false
          this.endMinuteList[+this.endTime.slice(3, 5)].active = true
        }
      },
      immediate: true
    },
    endTime: {
      handler (val) {
        let lastStart = this.startTime
        if (val.slice(0, 2) < this.startTime.slice(0, 2) || (val.slice(0, 2) == this.startTime.slice(0, 2) && val.slice(3, 5) <= this.startTime.slice(3, 5))) {
          this.startTime = val
          // 时
          let findIndex = this.endTimeList.findIndex(ele => !ele.disabled)
          let time = +this.startTime.slice(0, 2) - findIndex
          this.scrollToPosition('timePiker_list_start_time', 36 * time)
          this.startTimeList[+lastStart.slice(0, 2)].active = false
          this.startTimeList[+this.startTime.slice(0, 2)].active = true
          // 分
          this.scrollToPosition('timePiker_list_start_minute', +this.startTime.slice(3, 5) * 36)
          this.startMinuteList[+lastStart.slice(3, 5)].active = false
          this.startMinuteList[+this.startTime.slice(3, 5)].active = true
        }
      },
      immediate: true
    }
  },

  computed: {},

  methods: {
    OpenSelect(){
      if (!this.disabled) {
        this.visible = true
        this.$nextTick(() => {
          this.scrollToPosition('timePiker_list_start_time', (+this.startTime.slice(0, 2) - (this.bool ? 8 : 0)) * 36)
          this.scrollToPosition('timePiker_list_start_minute', +this.startTime.slice(3, 5) * 36)
          this.scrollToPosition('timePiker_list_end_time', (+this.endTime.slice(0, 2) - (this.bool ? 8 : 0)) * 36)
          this.scrollToPosition('timePiker_list_end_minute', +this.endTime.slice(3, 5) * 36)
        })
      }
    },
    fn(e) {
      // 获取被点击的元素
      if (!this.disabled) {
        const clickedEl = e.target;
        const el = document.querySelector(`.timePiker_select`)
        // `el` 是你正在检测外部点击的元素
        if (el.contains(clickedEl)) {
          console.log('内部');
        } else {
          console.log('外部');
          // 关闭弹窗,传回数据,并校验内容
          this.$emit('changeTime', this.startTime, this.endTime)
          this.visible = false
        }
      }
    },
    scrollToPosition (element, position) {
      const list = document.querySelector(`.${element}`)
      list.scrollTop = position
    },
    selectItem(element, index, type) {
      if (!type) {
        if (element == 'timePiker_list_start_time') {
          this.startTime = (index > 9 ? index + '' : '0' + index) + this.startTime.substring(2, 5)
          this.startTimeList.forEach((ele)=>{
            ele.active = false
          })
          this.startTimeList[index].active = true
          this.scrollToPosition(element, 36 * (index - this.startTimeList.findIndex(ele=>!ele.disabled)))
        } else if (element == 'timePiker_list_start_minute') {
          this.startTime = this.startTime.slice(0, 3) + (index > 9 ? index + '' : '0' + index)
          this.startMinuteList.forEach((ele) => {
            ele.active = false
          })
          this.startMinuteList[index].active = true
          this.scrollToPosition(element, 36 * (index - this.startMinuteList.findIndex(ele => !ele.disabled)))
        } else if (element == 'timePiker_list_end_time') {
          this.endTime = (index > 9 ? index + '' : '0' + index) + this.endTime.substring(2, 5)
          this.endTimeList.forEach((ele) => {
            ele.active = false
          })
          this.endTimeList[index].active = true
          this.scrollToPosition(element, 36 * (index - this.endTimeList.findIndex(ele => !ele.disabled)))
        } else {
          this.endTime = this.endTime.slice(0, 3) + (index > 9 ? index + '' : '0' + index)
          this.endMinuteList.forEach((ele) => {
            ele.active = false
          })
          this.endMinuteList[index].active = true
          this.scrollToPosition(element, 36 * (index - this.endMinuteList.findIndex(ele => !ele.disabled)))
        }
      }
    }
  }
}

</script>
<style lang="less" scoped>
.timePiker_box {
  position: relative;
}
.timePiker {
  width: 100%;
  background-color: #FFFFFF;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #DCDFE6;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  font-size: inherit;
  height: 32px;
  line-height: 32px;
  outline: none;
  padding: 0 15px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  width: 100%;
  display: inline-flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  padding: 3px 10px;
  .el-icon-time {
    display: inline-block;
    width: 25px;
    text-align: center;
  }
  .timePiker_icon {
    display: inline-block;
    height: 100%;
    padding: 0 5px;
    margin: 0;
    text-align: center;
    line-height: 24px;
    font-size: 14px;
    width: 5%;
    color: #303133;
  }
  .timePiker_input {
    border: none;
    outline: 0;
    display: inline-block;
    height: 100%;
    margin: 0;
    padding: 0;
    width: 39%;
    text-align: center;
    font-size: 14px;
    color: #606266;
  }
}
.timePiker:hover {
  border: 1px solid #407fff;
}
.timePiker_select {
  display: flex;
  justify-content: space-evenly;
  position: absolute;
  top: -246px;
  left: 0;
  background-color: #fff;
  z-index: 2024;
  width: 350px;
  height: 240px;
  overflow: hidden;
  border: 1px solid #e8eaec;
  box-shadow: 0 1px 6px rgba(0,0,0,.2);
  border-radius: 2px;
  .timePiker_list {
    height: 178px;
    overflow: scroll;
  }
  .timePiker_list::-webkit-scrollbar {
    display: none;
  }
  .selectBox {
    display: flex;
    border: 1px solid #e8eaec;
    border-radius: 4px;
  }
  ul {
    padding: 0 0 142px;
    li {
      width: 100%;
      height: 24px;
      line-height: 24px;
      margin: 0;
      padding: 6px 30px;
      box-sizing: content-box;
      text-align: left;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      cursor: pointer;
      list-style: none;
      transition: background .2s ease-in-out;
    }
    li :hover {
      background-color: #f3f3f3;
    }
    
    .disabled {
      cursor: not-allowed;
      color: #c0c4cc;
      background-image: none;
      background-color: #fff;
      opacity: 1;
      filter: alpha(opacity=100);
    }
    .active {
      color: #409eff;
      background-color: #f3f3f3;
      font-weight: 600;
    }
  }
  ul :hover {
    background-color: #f3f3f3;
  }
  .timePiker_select_title {
    text-align: center;
  }
}
.readonly {
  cursor: not-allowed;
  background-color: #f5f5f5;
  .timePiker_input {
    background-color: #f5f5f5;
  }
  .timePiker_icon, .el-icon-time, .timePiker_input {
    color: #C0C4CC;
  }
  .timePiker:hover {
    border: 1px solid #ccc;
  }
}
.timePiker:hover {
  border: 1px solid #DCDFE6;
}
</style>
  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值