按年、月、日为单位的日期范围选择器

 需求:实现用户以日、月、年为单位来选择时间范围

(描述:如果select选择框选中日,后面的日期选择器就是以日为单位的;select选择框选中月,右侧的日期选择器则是以月为单位的;select选择框选中年,右侧的日期选择器就是以年为单位的)

elementUI提供了现成的以日和月的单位来选择的时间选择器,但是没有按年选择的时间选择器。

效果如下:

按日、月、年选择日期范围

 

yearPicker完整代码如下:

<template>
  <div class="yearPicker" ref="yearPicker" :width="width"  @mouseenter="onHover"
      @mouseleave="onleave">
    <svg-icon icon-class="date-copy" style="fontSize:8px;" class="dateIcon"/>
    <input class="_inner" ref="inputLeft" v-model="startShowYear" @focus="onFocus" @blur="onBlur" type="text"
      name="yearInput" @keyup="checkStartInput($event)" placeholder="开始年份" readonly="readonly"/>
    <span class="toStyle">{{ sp }}</span>
    <input class="_inner" ref="inputRight" v-model="endShowYear" @focus="onFocus" @blur="onBlur" type="text"
      name="yearInput" @keyup="checkEndInput($event)" placeholder="结束年份" readonly="readonly"/>
      <svg-icon
      v-show="isHover"
      icon-class="clearAll"
      style="position: absolute; right: 8px; top: 8px; fontSize: 8px;"
      @click="clearDate"
    ></svg-icon>
      <div class="_inner floatPanel" v-if="showPanel">//快捷键
      <div style="width:70px;">
        <div class="item-left" @click="onChooseYear(1)">最近一年</div>
        <div class="item-left" @click="onChooseYear(3)">最近三年</div>
        <div class="item-left" @click="onChooseYear(5)">最近五年</div>
      </div>
      <div class="_inner leftPanel">
        <div class="_inner panelHead">
          <i class="_inner el-icon-d-arrow-left " @click="onClickLeft"></i>
          {{ leftYearList[0] + "-" + leftYearList[9] }}
        </div>
        <div class="_inner panelContent">

          <div :class="{
            oneSelected: item === startYear && oneSelected,
            startSelected: item === startYear,
            endSelected: item === endYear,
            betweenSelected: item > startYear && item < endYear,
            beyondSelected:item < nowYear - 19 || item > nowYear
          }" v-for="item in leftYearList" :key="item">
            <a :class="{
              cell: true,
              _inner: true,
              selected: item === startYear || item === endYear,
            }" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
              {{ item }}
            </a>
          </div>
        </div>
      </div>
      <div class="_inner rightPanel">
        <div class="_inner panelHead">
          <i class="_inner el-icon-d-arrow-right" @click="onClickRight"></i>
          {{ rightYearList[0] + "-" + rightYearList[9] }}
        </div>
        <div class="_inner panelContent">
          <div :class="{
            startSelected: item === startYear,
            endSelected: item === endYear,
            betweenSelected: item > startYear && item < endYear,
            beyondSelected:item < nowYear - 19 || item > nowYear
          }" v-for="item in rightYearList" :key="item">
            <a :class="{
              cell: true,
              _inner: true,
              selected: item === endYear || item === startYear,
            }" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
              {{ item }}
            </a>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
 
<script>
import moment from "moment";

const SELECT_STATE = {
  unselect: 0,
  selecting: 1,
  selected: 2,
};
export default {
  name: "yearPicker",


  computed: {
     nowYear(){
      let date=new Date();
      return date.getFullYear();
    },
    oneSelected() {
      return (
        this.curState === SELECT_STATE.selecting &&
        (this.startYear === this.endYear || this.endYear == null)
      );
    },
    startDate() {
      return this.startYear;
    },
    leftYearList() {
      return this.yearList.slice(0, 10);
    },
    rightYearList() {
      return this.yearList.slice(10, 20);
    },
    startYearFormat() {
      if (this._.isNil(this.startYear)) {
        return null;
      }
      return moment(this.startYear).startOf("year").format("yyyy");
    },
    endYearFormat() {
      if (this._.isNil(this.endYear)) {
        return null;
      }
      return moment(this.endYear).endOf("year").format("yyyy");
    },
  },
  props: {
    width: {
      default: 100,
    },
    labelWidth: {
      default: 35,
    },
    // labelText: {
    //   default: "时间标签",
    // },
    sp: {
      default: "至",
    },
    initYear: {
      default: null,
    },
  },
  data() {
    return {
      itemBg: {},
      startShowYear: null,
      endShowYear: null,
      yearList: [],
      showPanel: false,
      startYear: null,
      endYear: null,
      curYear: 0,
      curSelectedYear: 0,
      curState: SELECT_STATE.unselect,
      isHover: false,
    };
  },
  methods: {
    onHover() {
      this.isHover = true;
    },
    onleave(){
      this.isHover = false;
    },
    clearDate() {
      this.startYear = null;
      this.endYear = null;
      this.startShowYear = null;
      this.endShowYear = null;
      this.$emit("updateTimeRange", {
        startYear: null,
        endYear: null,
      });
    },
    onChooseYear(year) {
      let vm = this;
      vm.endYear = parseInt(moment().format("yyyy"));
      vm.startYear = moment().format("yyyy") - year;
      vm.startShowYear = vm.startYear;
      vm.endShowYear = vm.endYear;

      this.$emit("updateTimeRange", {
          startYear: moment(this.startYear + "")
            .startOf("year")
            .valueOf(),
          endYear:
            moment(this.endYear + "")
              .endOf("year")
              .valueOf() ,
        });
    },

    checkStartInput(event) {
      if (isNaN(this.startShowYear)) {
        this.startShowYear = this.startYear;
      } else {
        this.startYear = this.startShowYear * 1;
        this.changeYear();
      }
    },

    checkEndInput() {
      if (isNaN(this.endShowYear)) {
        this.endShowYear = this.endYear;
      } else {
        this.endYear = this.endShowYear * 1;
        this.changeYear();
      }
    },
    changeYear() {
      if (this.startYear > this.endYear) {
        let tmp = this.endYear;
        this.endYear = this.startYear;
        this.startYear = tmp;
        this.startShowYear = this.startYear;
        this.endShowYear = this.endYear;
      }
      if (this.startYear && this.endYear) {
        this.$emit("updateTimeRange", {
          startYear: moment(this.startYear + "")
            .startOf("year")
            .valueOf(),
          endYear:
            moment(this.endYear + "")
              .endOf("year")
              .valueOf() ,
        });
      } else {
        console.warn("WARN:年份不合法", this.startYear, this.endYear);
      }
    },
    onHoverItem(iYear) {
      if (this.curState === SELECT_STATE.selecting) {
        let tmpStart = this.curSelectedYear;
        this.endYear = Math.max(tmpStart, iYear);
        this.startYear = Math.min(tmpStart, iYear);
      }
    },
    onClickItem(iYear) {
      if (
        this.curState === SELECT_STATE.unselect ||
        this.curState === SELECT_STATE.selected
      ) {
        this.startYear = iYear;
        this.curSelectedYear = iYear;
        this.endYear = iYear;
        this.curState = SELECT_STATE.selecting;
      } else if (this.curState === SELECT_STATE.selecting) {
        this.endShowYear = this.endYear;
        this.startShowYear = this.startYear;
        this.curState = SELECT_STATE.selected;
        this.$emit("updateTimeRange", {
          startYear: moment(this.startYear + "")
            .startOf("year")
            .valueOf(),
          endYear:
            moment(this.endYear + "")
              .endOf("year")
              .valueOf() ,
        });

        setTimeout(() => {
          //为动画留的时间,可优化
          this.showPanel = false;
        }, 300);
      }
    },
    onFocus() {
      this.$nextTick(() => {
        this.showPanel = true;
      });
    },
    onBlur() {
      //   this.showPanel = false;
    },
    updateYearList() {
      let iStart = Math.floor(this.curYear / 10) * 10 - 10;
      iStart = iStart < 0 ? 0 : iStart;
      this.yearList = [];
      for (let index = 0; index < 20; index++) {
        this.yearList.push(iStart + index);
      }
    },
    closePanel(e) {
      if (!this.showPanel) {
        return;
      }
      if (typeof e.target.className !== "string") {
        this.$nextTick(() => {
          this.showPanel = false;
        });
        return;
      }
      if (
        e.target.className.indexOf("_inner") === -1 ||
        (e.target.name === "yearInput" &&
          e.target !== this.$refs.inputLeft &&
          e.target !== this.$refs.inputRight)
      ) {
        this.$nextTick(() => {
          this.showPanel = false;
        });
      }

      e.stopPropagation();
      return false;
    },
    onClickLeft() {
      this.curYear = this.curYear * 1 - 10;
      this.updateYearList();
    },
    onClickRight() {
      this.curYear = this.curYear * 1 + 10;
      this.updateYearList();
    },

    //------------------对外接口------------------------
    //直接传时间戳
    setYear(startYearStamp, endYearStamp) {
      if (!isNaN(startYearStamp) && !isNaN(endYearStamp)) {
        let startYear = moment(startYearStamp).format("yyyy");
        let endYear = moment(endYearStamp).format("yyyy");
        this.startYear = startYear * 1;
        this.endYear = endYear * 1;
        this.endShowYear = endYear * 1;
        this.startShowYear = startYear * 1;
      }
    },
  },

  created() {
    this.curYear = moment().format("yyyy");
    this.updateYearList();
  },
  beforeUnmount() {
    document.removeEventListener("click", this.closePanel.bind(this));
  },

  mounted() {
    this.$refs.yearPicker.style = "padding-left:" + this.labelWidth + "px";
    document.addEventListener("click", this.closePanel.bind(this));
  },
};
</script>
<style lang="scss" scoped>
.yearPicker {
  font-size: 14px;
  display: flex;
  position: relative;
  transition: all 0.3s;
  width: 275px;
  height: 33px;
  border: 1px solid #B3B3B3;

  input:first-child {
    text-align: right;
  }

  .dateIcon {
    position: absolute;
    left: 8px;
  }

  background-color: #fff;

  span {
    padding: 0 20px;
    height: 32px;
    line-height: 32px;
  }

  border: 1px solid #eff1f3;
  height: 34px;
  line-height: 34px;
  border-radius: 4px;
  padding: 0 28px 0 8px;
  box-sizing: border-box;

  .floatPanel {
    >div {
      width: 50%;
    }

    padding: 0 16px;
    position: absolute;
    display: flex;
    background-color: #fff;
    z-index: 2000;
    border-radius: 4px;
    width: 650px;
    height: 1.3982rem;
    top: -1.459rem;
    left: -0.4073rem;
    box-shadow: 0 2px 12px 0 rgba(195, 195, 195, 10%);

    .panelContent {
      display: flex;
      flex-wrap: wrap;
      width: 100%;
      height: calc(100% - 70px);

      .oneSelected {
        border-top-right-radius: 24px;
        border-bottom-right-radius: 24px;
      }

      .startSelected {
        background-color: #f6f6f7;
        border-top-left-radius: 24px;
        border-bottom-left-radius: 24px;
      }

      .endSelected {
        background-color: #f6f6f7;
        border-top-right-radius: 24px;
        border-bottom-right-radius: 24px;
      }
      .beyondSelected{
        background-color: #F5F7FA;
        color:#C2C6CE;
        pointer-events: none;
      }

      .betweenSelected {
        background-color: #f6f6f7;
      }

      >div {
        width: 75px;
        height: 48px;
        line-height: 48px;
        margin: 3px 0;
        // border-radius: 24px;
        text-align: center;

        a {
          display: inline-block;
          width: 60px;
          height: 36px;
          cursor: pointer;
          line-height: 36px;
          border-radius: 18px;
        }

        .selected {
          background-color: #3e77fc;
          color: #fff;
        }
      }
    }

    .panelHead {
      position: relative;
      height: 46px;
      line-height: 46px;
      text-align: center;

      i {
        position: absolute;
        cursor: pointer;

        &:hover {
          color: #3e77fc;
        }
      }
    }

    .rightPanel {
      padding-left: 8px;
    }

    .leftPanel .panelHead i {
      left: 20px;
      //   vertical-align: middle;
      line-height: 46px;
    }

    .rightPanel .panelHead i {
      right: 20px;
      line-height: 46px;
      //   vertical-align: middle;;
    }
  }

  .floatPanel::before {
    content: "";
    height: 100%;
    position: absolute;
    left: 50%;
    width: 1px;
    border-left: 1px solid #e4e4e4;
  }
}

input {
  width: 60px;
  border: none;
  height: 32px;
  line-height: 32px;
  box-sizing: border-box;
  background-color: transparent;
}

input:focus {
  outline: none;
  background-color: transparent;
}

.yearPicker:hover {
  border-color: #3e77fc;
}

.dateIcon {
  position: absolute;
  right: 16px;
   top: 6px;
  color: #adb2bc;
}

.item-left {
  height: 30px;
  line-height: 30px;
  cursor: pointer;
  margin-top: 10px;
}
</style>
 

datePicker 完整代码如下:

<template>
  <div class="timePicker">
    <el-select
      v-model="value.dateType"
      placeholder="请选择"
      ref="dateType"
      style="width: 65px; height: 30px"
      class="selectType"
      :disabled="status"
      @change="onChangeTimePicker"
    >
      <el-option
        v-for="item in dateOptions"
        :key="item.value"
        :label="item.label"
        :value="item.value"
      >
      </el-option>
    </el-select>

    <div
      class="block showtime"
      v-if="value.dateType === 'day'"
      style="margin-left: -1px"
    >
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        align="right"
        unlink-panels
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        :picker-options="pickerDayOptions"
        style="width: 300px"
        class="datePicker"
        :disabled="status"
        format="yyyy-MM-dd"
        value-format="yyyy-MM-dd"
      >
      </el-date-picker>
    </div>

    <div class="block" v-else-if="value.dateType === 'month'">
      <el-date-picker
        v-model="dateRange"
        type="monthrange"
        align="right"
        unlink-panels
        range-separator="至"
        start-placeholder="开始月份"
        end-placeholder="结束月份"
        :picker-options="pickerMonthOptions"
        style="width: 300px"
        class="monthPicker"
        format="yyyy-MM"
        value-format="yyyy-MM"
      >
      </el-date-picker>
    </div>

    <div class="block" v-else-if="value.dateType === 'year'">
      <yearPicker
        style="width: 300px; height: 32px"
        ref="statisticPicker"
        :initYear="dateRange"
        @updateTimeRange="updateStatisticYear"
        :width="300"
      />
    </div>
  </div>
</template>

<script>
import yearPicker from "./yearPicker.vue";
import moment from "moment";

export default {
  components: {
    yearPicker,
  },
  props: {
    value: {
      type: Object,
    },
    status: {
      type: Boolean,
    },
  },
  watch: {
    status(newValue, oldValue) {
      if (newValue) {
        this.dateRange = [];
        this.value.dateType = "day";
      }
    },

    dateRange(newValue, oldValue) {
      if (newValue) {
        this.value.startTime = newValue[0];
        this.value.endTime = newValue[1];
      } else {
        this.value.startTime = "";
        this.value.endTime = "";
      }
      // this.$emit('getDateType',this.value.dateType)
      // console.log("+++",this.value.dateType)
    },
  },
  data() {
    return {
      dateType: "day",
      dateOptions: [
        {
          value: "day",
          label: "日",
        },
        {
          value: "month",
          label: "月",
        },
        {
          value: "year",
          label: "年",
        },
      ],
      dateRange: [],
      pickerDayMinDate: "",
      pickerDayOptions: {
        shortcuts: [
          {
            text: "最近一周",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(moment(start).subtract(7, "days").valueOf());
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近一个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(moment(start).subtract(1, "months").valueOf());
              picker.$emit("pick", [start, end]);
            },
          },
          // {
          //   text: "最近三个月",
          //   onClick(picker) {
          //     const end = new Date();
          //     const start = new Date();
          //     start.setTime(moment(start).subtract(3, "months").valueOf());
          //     picker.$emit("pick", [start, end]);
          //   },
          // },
        ],
        onPick: ({ maxDate, minDate }) => {
          this.pickerDayMinDate = minDate.getTime();
          if (maxDate) {
            this.pickerDayMinDate = "";
          }
        },
        disabledDate: (time) => {
          if (this.pickerDayMinDate !== "") {
            let beforeDate = moment(this.pickerDayMinDate).subtract(59, "days");
            let afterDate = moment(this.pickerDayMinDate).add(59, "days");
            return (
              moment(time).isAfter(afterDate) ||
              moment(time).isBefore(beforeDate) ||
              time.getTime() > Date.now()
            );
          }
          return time.getTime() > Date.now();
        },
      },
      pickerMonthMinDate: "",
      pickerMonthOptions: {
        shortcuts: [
          {
            text: "本月",
            onClick(picker) {
              picker.$emit("pick", [new Date(), new Date()]);
            },
          },
          {
            text: "今年至今",
            onClick(picker) {
              const end = new Date();
              const start = new Date(new Date().getFullYear(), 0);
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近六个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setMonth(start.getMonth() - 6);
              picker.$emit("pick", [start, end]);
            },
          },
        ],
        onPick: ({ maxDate, minDate }) => {
          this.pickerMonthMinDate = minDate.getTime();
          if (maxDate) {
            this.pickerMonthMinDate = "";
          }
        },
        disabledDate: (time) => {
          if (this.pickerMonthMinDate !== "") {
            let beforeDate = moment(this.pickerMonthMinDate).subtract(
              19,
              "months"
            );
            let afterDate = moment(this.pickerMonthMinDate).add(19, "months");
            return (
              moment(time).isAfter(afterDate) ||
              moment(time).isBefore(beforeDate) ||
              time.getTime() > Date.now()
            );
          }
          return time.getTime() > Date.now();
        },
      },
    };
  },
  methods: {
    onChangeTimePicker(dateType){
      debugger
      this.dateRange = [];
      this.value.startTime ='';
      this.value.endTime ='';
    },
    updateStatisticYear(dateRange) {
      if(dateRange.startYear){
      this.value.startTime = moment(dateRange.startYear).format("yyyy");
      this.value.endTime = moment(dateRange.endYear).format("yyyy");}
      if(!dateRange.startYear){
        this.value.startTime=null;
        this.value.endTime=null;
      }
    },
  },

  created() {
    // 初始化数据
    if (this.value) {
      this.value.dateType = this.value.dateType ? this.value.dateType : "day";
      this.value.startTime = this.value.startTime
        ? this.value.startTime
        : undefined;
      this.value.endTime = this.value.endTime ? this.value.endTime : undefined;

      if (this.value.startTime && this.value.endTime) {
        this.dateRange = [this.value.startTime, this.value.endTime];
      } else {
        this.dateRange = [];
      }
    } else {
      this.value = {
        dateType: "day",
        startTime: undefined,
        endTime: undefined,
      };
      this.dateRange = [];
    }
  },
  mounted() {
    document
      .getElementsByClassName("el-range-input")[1]
      .classList.remove("input-margin");
    document
      .getElementsByClassName("el-range-input")[1]
      .classList.add("input-margin1");
  },
};
</script>

<style lang="scss" scoped>
.timePicker {
  width: 460px;
  display: flex;

  .selectType {
    width: 61px;
    height: 31px;
    margin-right: 5px;
  }

  .showtime {
    flex: 1;
    margin-left: 5px;
    .el-date-editor.el-input,
    .el-date-editor.el-input__inner {
      width: 280px;
    }
    .input-margin1 {
      //  .el-range-editor--medium .el-range-input {font-size: 0.07292rem;
      // margin-left: 5px;
      font-size: 0.07292rem;
      margin-left: 5px;
    }
  }
}
</style>
<style>
.datePicker .el-range-separator {
  width: 56px !important;
  color: #696b6f;
}
.monthPicker .el-range-separator {
  width: 56px !important;
  color: #696b6f;
}
.timePicker .el-input__inner {
  height: 35px !important;
}
</style>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值