vue时间组件DatePicker组件的手写示例

概述

在日常工作中,比不可少会用到时间组件,我们的第一反应就是直接到组件库去找一下现成的来用下,毕竟,时间组件看起来还是很复杂的,对于没接触过的人来说,要自己去写一个这样的组件出来,还是有难度的,但是作为一名前端开发,这么常见的组件,我们还是值得取自己写一个这样的组件的,现在就手把手带你实现vue中的DatePicker组件。看完不会找我。

前置知识

在开始写代码之前,建议代价先看看时间组件的布局,目前主流的组件库iview ,element等提供的时间组件布局都基本类似,功能也差不多,因此在这里实现的组件库的布局也也element家的布局差不多,大家先看下布局的最终样子。 这是日的时间组件,当我们这个能实现的时候,像那些年,月的就更简单了,因此这里我们只实现一个,其他的可以自己扩展。

布局方面

  • 我们可以看到最终时间组件可以拆分为两大部分,最上面可以拆分为一个时间切换的组件,下面为一个table,用于记录日期,下面的表格我们又可以拆分为上面的星期组件,和下面的日期具体实现组件。
  • 然后,我们注意观察,表格是一个6行七列共计42个单元格的布局形式,包含上月的剩余天数,当前月份的全部天数,以及下月的开始天数,加起来组成42个单元格。其中上月和下月我们布局样式区别于当前月份的布局,还有就是当前天数的那个日期,我们需要高亮显示。

具体的实现思路

  • 在清楚布局之后,我们需要根据用户传入的时间,生成一个6*7=42的天数的td单元格,在这42个单元格中,包含上月剩余的天数,当前月份的全部天数,下月的开始天数。
  • 如果你清楚了步骤一,那么我们接下来就容易多了,我们要计算上月的天数,当前月份的全部天数,下月的开始天数,以及当前月份1号星期几。
  • 由于头部是星期日,星期一,星期二,星期三,星期四,星期五,星期六的布局,因此我们需要计算当前月份1号星期几,这样我们就能找到上月的剩余天数了,下月的剩余天数就等于42-当前月份天数-上月剩余天数。
  • 最后我们就需要提供计算月份天数,月份1号星期几,以及一个生成对应数据的工具函数了。

具体实现

目录结构

utils.js(工具函数(核心))

  • .components/DatePicker/utils.js
/**
 * @Description  获取当前月份的天数
 * @param { Array } 年月组成的数组,例如:[2022,7]
 * @return {Number}例如:2022年7月有31天 返回31
 **/
export function getCurrentMonthCount([year, month]) {
  // 当我们实例化Date函数的时候,传入第三个参数为0可以通过getDate获取到当前月份具体有多少天
  return new Date(year, month, 0).getDate();
}
/**
 * @Description  获取当前月份1号是星期二几
 * @param { Array } 年月组成的数组,例如:[2022,7]
 * @return {Number}  例如2022-7-1是星期5,返回5
 **/
export function getFirstMonthDayWeek([year, month]) {
  return new Date(year, month - 1, 1).getDay();
}
/**
 * @Description  根据年月,组装渲染天的表格数据
 * @param { Array } 年月组成的数组,例如:[2022,7]
 * @return {Array}
 **/
/*
在这里介绍下我们时间组件写法的思路:
1.对于时间组件的布局,可以先去参考iview element等开源组件库的date-picker组件的布局,基本上都是一样的
2.在清楚布局之后,我们需要根据用户传入的时间,生成一个6*7=42的天数的td单元格,在这42个单元格中,包含上月剩余的天数,当前月份的全部天数,下月的开始天数
3.如果你清楚了步骤二,那么我们接下来就容易多了,我们要计算上月的天数,当前月份的全部天数,下月的开始天数,以及当前月份1号星期几
4.由于头部是星期日,星期一,星期二,星期三,星期四,星期五,星期六的布局,因此我们需要计算当前月份1号星期几,这样我们就能找到上月的剩余天数了,下月的剩余天数就等于42-当前月份天数-上月剩余天数
5.在上面步骤知道后我们就可以着手根据上面提供的工具函数,生成我们需要的表格数据了
最终生成的是6*7的二维数组,因为表格天数的布局为6*7的布局,数据格式如下:
数组的个数代表了渲染的列数,内部每项数组代表每列的td个数
[
  [
    {
      //代表当前的td几号
       value: xxx,
       //上个月的号数和下个月的号数标识下,渲染的时候,我们样式另外布局
        disbled: true,
        //当前td的时间格式,用于点击了,给input显示以及供用户使用格式为2022-7-22
        date: xxx,
        // 当前天的时间td,我们需要高亮显示。添加标识
        active:xxx,
        //当前td的索引
        index: xxx,
    },
    {},
    {},
    {},
    {},
    {},
    {}
  ],
  [],
  [],
  [],
  [],
  [],
]
*/
export function genarateDayData([year, month]) {
  // 获取上月天数
  let lastMonthCount = getCurrentMonthCount([year, month - 1]);
  // 获取当月天数
  let currentMonthCount = getCurrentMonthCount([year, month]);
  // 获取当月1号星期
  let currentMonthFirstDayWeek = getFirstMonthDayWeek([year, month]);
  let dayList = [];
  let lastMonthPointer = 1;
  let currentMonthPoiner = 1;
  let nextMonthPointer = 1;
  // 根据日期组件的天数布局,共计42天,包含上月剩余天数+当月天数+下月初始天数
  for (let i = 0; i < 42; i++) {
    // 上个月需要渲染的td个数,以及对应的值
    if (lastMonthPointer <= currentMonthFirstDayWeek) {
      // 上月
      dayList.unshift({
        value: lastMonthCount--,
        disbled: true,
        date: year + "-" + (month - 1) + "-" + (lastMonthCount + 1),
        index: i,
      });
      lastMonthPointer++;
    } else if (currentMonthPoiner <= currentMonthCount) {
      // 当月
      dayList.push({
        value: currentMonthPoiner++,
        disbled: false,
        active:
          new Date().getFullYear() == year &&
          new Date().getMonth() + 1 == month &&
          currentMonthPoiner - 1 == new Date().getDate(),
        date: year + "-" + month + "-" + (currentMonthPoiner - 1),
        index: i,
      });
    } else {
      // 下月
      dayList.push({
        value: nextMonthPointer++,
        disbled: true,
        date: year + "-" + (month + 1) + "-" + (nextMonthPointer - 1),
        index: i,
      });
    }
  }
  // 当前天数高亮
  // 最后将数据生成二维数组返回:对应的就是6*7的二维数组用于渲染天数表格列
  let result = [];
  let index = 1;
  let i = 0;
  while (index <= 6) {
    let arr = [];
    for (let j = 0; j < 7; j++) {
      arr.push(dayList[i]);
      i++;
    }
    result.push(arr);
    index++;
  }
  return result;
}

constant.js

  • .components/DatePicker/constant.js(常量文件)
//用于保存组建的常量,静态数据,比如表头的星期
export const weekList = ["日", "一", "二", "三", "四", "五", "六"];

DatePicker.vue

  • .components/DatePicker/DatePicker.vue(整体包裹组件供外部使用)
<template>
  <div class="date-picker-wrap">
    <div class="date-eidtor">
      <!-- 显示时间的input -->
      <input
        type="text"
        :placeholder="placeholder"
        class="date-edit-input"
        v-model="currentDate"
        @click.stop="showDatePannel = !showDatePannel"
      />
    </div>
    <!-- 面包通过过渡组件包裹,实现显示隐藏友好过渡 -->
    <transition name="date-picker">
      <div class="date-pocker-panel" v-show="showDatePannel">
        <!-- 时间控件的头部,用户切换年月 -->
        <date-picker-head
          @dateRangeChange="dateRangeChange"
          :date="curDate"
        ></date-picker-head>
        <!-- 主要的时间显示列表组件,用于显示对应月份的时间 -->
        <date-table :list="list" @dateChange="dateChange"></date-table>
      </div>
    </transition>
  </div>
</template>
<script>
import { genarateDayData } from "./utils";
import DatePickerHead from "./DatePickerHead.vue";
import DateTable from "./DateTable.vue";
export default {
  components: {
    DatePickerHead,
    DateTable,
  },
  props: {
    // 输入框提示
    placeholder: {
      type: String,
      default: "选择时间",
    },
    // 时间,为Date类型,默认为当前时间
    date: {
      type: Date,
      default() {
        return new Date();
      },
    },
  },
  data() {
    return {
      // 用于控制面包显示与隐藏
      showDatePannel: false,
      // 表格数据
      list: [],
      // 处理props时间为数组格式[年,月]
      curDate: [this.date.getFullYear(), this.date.getMonth() + 1],
      // 用户input显示时间
      currentDate: "",
    };
  },
  mounted() {
    // 获取当前月份的时间数据
    this.getDateList();
    // 除开时间组件的其他地方点击,关闭时间面板
    window.addEventListener("click", () => {
      this.showDatePannel = false;
    });
  },
  methods: {
    // 监听每个td时间项点击
    dateChange(date) {
      this.$emit("dateChange", date);
      this.showDatePannel = false;
      this.currentDate = date;
    },
    // 头部年月切换
    dateRangeChange(type) {
      switch (type) {
        // 上一年点击
        case "lastYear":
          this.curDate = [this.curDate[0] - 1, this.curDate[1]];
          break;
        // 上一月点击(月份<1,就要返回到上一年的12月份)
        case "lastMonth":
          this.curDate = [
            this.curDate[1] - 1 <= 0 ? this.curDate[0] - 1 : this.curDate[0],
            this.curDate[1] - 1 <= 0 ? 12 : this.curDate[1] - 1,
          ];
          break;
        // 下一年点击
        case "nextYear":
          this.curDate = [this.curDate[0] + 1, this.curDate[1]];
          break;
        case "nextMonth":
          // 下一月点击(月份>12,就要到下一年的一月份)
          this.curDate = [
            this.curDate[1] + 1 > 12 ? this.curDate[0] + 1 : this.curDate[0],
            this.curDate[1] + 1 > 12 ? 1 : this.curDate[1] + 1,
          ];
          break;
      }
      this.getDateList();
    },
    // 通过props传递的时间,组装成长度为42的数组,具体看utils文件下下面的这个方法
    getDateList() {
      this.list = genarateDayData(this.curDate);
    },
  },
};
</script>
<style lang="less">
.date-picker-wrap {
  position: relative;
  .date-pocker-panel {
    position: absolute;
    left: 0;
    top: 50px;
    width: 324px;
    height: 343px;
    color: #606266;
    border: 1px solid #e4e7ed;
    box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
    background: #fff;
    border-radius: 4px;
    line-height: 30px;
    padding: 12px;
    text-align: center;
  }
  .date-eidtor {
    width: 220px;
    .date-edit-input {
      background-color: #fff;
      background-image: none;
      border-radius: 4px;
      border: 1px solid #dcdfe6;
      box-sizing: border-box;
      color: #606266;
      display: inline-block;
      font-size: inherit;
      height: 40px;
      line-height: 40px;
      outline: none;
      padding: 0 15px;
      transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
      width: 100%;
    }
  }
  .date-picker-enter-active,
  .date-picker-leave-active {
    transition: all 0.25s ease;
  }
  .date-picker-enter,
  .date-picker-leave-to {
    opacity: 0;
    height: 0;
  }
}
</style>

DatePickerHead.vue

  • .components/DatePicker/DatePickerHead.vue(顶部时间切换组件)
<template>
  <!-- 顶部的时间切换组件 -->
  <div class="date-picker-head">
    <div class="arrow-left">
      <!-- 上一年点击 -->
      <span class="last-year arrow" @click.stop="toogleDate('lastYear')"></span>
      <!-- 上一月点击 -->
      <span class="last-month arrow" @click.stop="toogleDate('lastMonth')"></span>
    </div>
    <!-- 显示当前的年和月 -->
    <div class="date-content">{{ date[0] + "年" + date[1] + "月" }}</div>
    <div class="arrow-right">
      <!-- 下一月点击 -->
      <span class="next-month arrow" @click.stop="toogleDate('nextMonth')"></span>
      <!-- 下一年点击 -->
      <span class="next-year arrow" @click.stop="toogleDate('nextYear')"></span>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    // 时间
    date: {
      type: Array,
      default() {
        return [new Date().getFullYear(), new Date().getMonth() + 1];
      },
    },
  },
  methods: {
    // 派发table事件处理逻辑,参数为当前td的时间,格式为2022-7-22
    toogleDate(type) {
      this.$emit("dateRangeChange", type);
    },
  },
};
</script>
<style lang="less">
.date-picker-head {
  display: flex;
  justify-content: space-between;
  margin-bottom: 12px;
  .last-year,
  .next-month {
    margin-right: 15px;
  }
  .arrow {
    cursor: pointer;
  }
}
</style>

DateTable.vue(表格组件)

  • .components/DatePicker/DateTable.vue
<template>
  <div class="date-table">
    <!-- 时间表格 -->
    <table>
      <!-- 顶部的星期组件 -->
      <date-picker-week-bar></date-picker-week-bar>
      <!-- 下方的6*7的月份天数组件 -->
      <date-picker-day-content
        :list="list"
        @dateChange="dateChange"
      ></date-picker-day-content>
    </table>
  </div>
</template>
<script>
import DatePickerWeekBar from "./DatePickerWeekBar.vue";
import DatePickerDayContent from "./DatePickerDayContent.vue";
export default {
  props: {
    // 表格数据
    list: {
      type: Array,
      default() {
        return [];
      },
    },
  },
  components: {
    DatePickerWeekBar,
    DatePickerDayContent,
  },
  methods: {
    // 派发td天数点击事件,参数为当前天数的时间格式为 2022-07-22
    dateChange(date) {
      this.$emit("dateChange", date);
    },
  },
};
</script>
<style lang="less">
.date-table {
  font-size: 12px;
}
</style>

DatePickerWeekBar.vue(表头组件,渲染星期)

  • .components/DatePicker/DatePickerWeekBar.vue
<template>
  <thead class="date-picker-week-bar">
    <tr>
      <th v-for="item in weekList" :key="item">{{ item }}</th>
    </tr>
  </thead>
</template>
<script>
import { weekList } from "./constant.js";
export default {
  data() {
    return {
      weekList,
    };
  },
};
</script>
<style lang="less">
.date-picker-week-bar {
  th {
    width: 42px;
    height: 42px;
    color: #606266;
    font-weight: 400;
    border-bottom: 1px solid #ebeef5;
  }
}
</style>

DatePickerDayContent.vue

表格主题内容组件,用于渲染具体日期

  • .components/DatePicker/DatePickerDayContent.vue
<template>
  <tbody class="date-picker-day-content">
    <tr v-for="(item, index) in list" :key="index">
      <td
        v-for="(subItem, index) in item"
        :key="index"
        :class="[
          subItem.disbled ? 'disble-item' : 'day-item',
          subItem.active ? 'active' : '',
          subItem.index == currentDay ? 'active-click' : '',
        ]"
        @click="handleDayClick(subItem)"
      >
        {{ subItem.value }}
      </td>
    </tr>
  </tbody>
</template>
<script>
export default {
  props: {
  //表格数据
    list: {
      type: Array,
      default() {
        return [];
      },
    },
  },
  data() {
    return {
    //当前点击项活跃高亮
      currentDay: -1,
    };
  },
  methods: {
    // 处理天的表格点击,触发关闭时间控件面板,设置时间input的值
    handleDayClick(item) {
      if (item.currentDay == item.index) return;
      this.currentDay = item.index;
      this.$emit("dateChange", item.date);
    },
  },
};
</script>
<style lang="less">
.date-picker-day-content {
  td {
    width: 40px;
    height: 40px;
    color: #606266;
    font-weight: 400;
    text-align: center;
    cursor: pointer;
  }
  .disble-item {
    cursor: not-allowed;
    color: #c0c4cc;
  }
  .day-item.active {
    color: #008c8c;
    font-weight: bold;
  }
  .day-item.active-click {
    border-radius: 50%;
    width: 30px;
    height: 30px;
    line-height: 30px;
    color: #fff;
    background-color: #008c8c;
  }
}
</style>

index.js(按需导出文件)

  • .components/DatePicker/index.js
import DatePicker from "./DatePicker.vue";
export { DatePicker };

使用

  • App.vue
<template>
  <div id="app">
    <div class="test-date-picker">
      <date-picker :date="date" @dateChange="dateChange"></date-picker>
    </div>
  </div>
</template>
<script>
import { DatePicker } from "./components/DatePicker/index";
export default {
  name: "App",
  components: {
    DatePicker,
  },
  data() {
    return {
      date: new Date(),
    };
  },
};
</script>
<style lang="less">
html,
body,
#app {
  height: 100%;
  width: 100%;
}
#app {
  .test-date-picker {
    width: 50%;
    margin: 20px auto;
  }
}
</style>

最终效果

总结

组件库很多看着很难的组件,只要我们认真斟酌,然后试着去是实现下,还是不难的,实现上面的这种类型的组件之后,其他的年和月类型的就更简答了,大家可以自己扩展,更多关于vue时间组件DatePicker组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue中datepicker的使用教程实例代码详解

    写这个文章主要是记录下用法,官网已经说的很详细了 npm install vue-datepicker --save html代码 <myDatepicker :date="startTime" :option="multiOption" :limit="limit"></myDatepicker> <myDatepicker :date="endtime" :option="timeo

  • vue2.0 datepicker使用方法

    1.使用vue-cli脚手架创建vue项目.在order列表页使用 vue-datepicker.按照文档操作,安装后,使用 myDatepicker from 'vue-datepicker'命令,导入组件.但是控制台提示 exports is not defined. 2.使用的是webpack包管理工具 1.导入代码 import myDatepicker from 'vue-datepicker' export default{ components: { VSelection, myD

  • vue项目中引入vue-datepicker插件的详解

    项目需求中有一个日期选择限制的功能点:今天之前不可选,周末不可选. 传统的input type='date无法做到,所以使用了这个插件来实现功能. 1.引入vue-datepicker loader:npm install vue-datepicker 2.引入moment loader:npm install moment --save 因为vue-datepicker是依赖vue和moment的,所以也应提前 引入moment: 3.在用到该插件的地方引入: import myDatepic

  • 使用Vue写一个datepicker的示例

    前言 写插件是很有意思,也很锻炼人,因为这个过程中能发现许多的细节问题.在前端发展的过程中,jQuery无疑是一个重要的里程碑,围绕着这个优秀项目也出现了很多优秀的插件可以直接使用,大大节省了开发者们的时间.jQuery最重要的作用是跨浏览器,而现在浏览器市场虽不完美,但已远没有从前那么惨,数据驱动视图的思想倍受欢迎,大家开始使用前端框架取代jQuery,我个人比较喜欢Vue.js,所以想试着用Vue.js写一个组件出来. 为了发布到npm上,所以给项目地址改名字了,但是内部代码没有改,使用方法

  • Vue引用第三方datepicker插件无法监听datepicker输入框的值的解决

    一.背景 在Vue项目中使用了第三方的datepicker插件,在选择日期后vue无法检测到datepicker输入框的变化 <label class="fl">日期:</label> <div class="input-wrapper fr"> <input class="daterangepicker" ref="datepicker" v-model="dateRang

  • vue时间组件DatePicker组件的手写示例

    概述 在日常工作中,比不可少会用到时间组件,我们的第一反应就是直接到组件库去找一下现成的来用下,毕竟,时间组件看起来还是很复杂的,对于没接触过的人来说,要自己去写一个这样的组件出来,还是有难度的,但是作为一名前端开发,这么常见的组件,我们还是值得取自己写一个这样的组件的,现在就手把手带你实现vue中的DatePicker组件.看完不会找我. 前置知识 在开始写代码之前,建议代价先看看时间组件的布局,目前主流的组件库iview ,element等提供的时间组件布局都基本类似,功能也差不多,因此在这

  • vue使用canvas实现移动端手写签名

    基于vue使用canvas实现移动端手写签名! 之前自己开发有这么一个需求,需要实现手写签名,然后以图片的形式保存生成图片的base64数据流 .自己在网上找了一堆,都不是很完美.然后参考网上的加自己的优化和修改做了一版.希望有需要的朋友可以拿来直接用. HTML部分: <template> <div class="hello" > <div>请输入您的签名7:</div> <canvas id="canvas"

  • Vue源码学习记录之手写vm.$mount方法

    目录 一.概述 二.使用方式 三.完整版vm.$mount的实现原理 四.只包含运行时版本的vm.$mount的实现原理 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.概述 在我们开发中,经常要用到Vue.extend创建出Vue的子类来构造函数,通过new 得到子类的实例,然后通过$mount挂载到节点,如代码: <div id="mount-point"></div> <!-- 创建构造器 --> var Profile =

  • Promise对象all与race方法手写示例

    目录 前言 Promise.all 介绍 手写 Promise.race 介绍 手写 前言 在理解了手写promsie.then的方法后,再来看它的其他方法,感觉真的简单了不少. Promise.all 介绍 Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例. const p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all()方法接受一个数组作为参数,p1.p2.p3都是 Promise 实例.另外

  • JS前端html2canvas手写示例问题剖析

    目录 前言 感性认识 第一步:解析 dom 树 第二步:按层叠规则分组(重点) 第三步:创建画布 第四步:渲染 另一种方法(html->svg->canvas) 结语 前言 这两天把 html2canvas 这玩意抽丝剥茧了一下,搞了个勉强能跑的小 demo,麻雀虽小五脏俱全,来看看实现的效果吧(跟基金一样的绿,离离原上谱)

  • 手写Vue内置组件component的实现示例

    目录 前言 内置组件component的使用 component组件的原理分析 虚拟DOM与原生DOM render函数的使用 尝试手写实现component 总结 最近在复习Vue的源码,今天带大家手写实现一下Vue内置组件component,比较简单,最近面试有被问到. 前言 Vue大家都很熟悉,除了原生的组件,其自己也封装了一下内置组件,比如component,transition,keep-alive等等. component算是用的比较多的了,当我们遇到需要根据不同条件显示不同组件的时

  • Vue手写实现组件初渲染

    目录 前言 生成虚拟节点 将虚拟节点处理为真实节点 总结 前言 在Vue进行文本编译之后,会得到代码字符串生成的render函数.本文会基于render函数介绍以下内容: 执行render函数生成虚拟节点 通过vm._update方法,将虚拟节点渲染为真实DOM 在vm.$mount方法中,文本编译完成后,要进行组件的挂载,代码如下: Vue.prototype.$mount = function (el) { // text compile code .... mountComponent(v

  • 手写可拖动穿梭框组件CustormTransfer vue实现示例

    目录 本文内容 最终效果图 组件html布局 穿梭框左侧内容 穿梭框右侧内容 穿梭框中间向左.向右按钮 把排序好的穿梭数据传给父组件 整体代码 小结 本文内容 需求是实现类似 el-transfer的组件,右侧框内容可以拖动排序: 手写div样式 + vuedraggable组件实现. 最终效果图 组件html布局 新建一个组件文件 CustormTransfer.vue,穿梭框 html 分为左中右三部分,使用flex布局使其横向布局,此时代码如下 <template> <div cl

  • VUE实现时间轴播放组件

    本文实例为大家分享了VUE实现时间轴播放组件的具体代码,供大家参考,具体内容如下 先上效果图吧 1.初始化的效果! 2.可以拖拽,鼠标放上显示时间 3.播放按钮后,正常播放 左右两个横线可以上一页下一页 下面说VUE接入的步骤: 1.index.html中引入js和css文件 <script src='../static/js/timePlay.js'></script> <link href='../static/css/timePlay.css' rel='stylesh

  • vue视频时间进度条组件使用方法详解

    本文实例为大家分享了vue视频时间进度条组件的使用方法,供大家参考,具体内容如下 有些视频是以视频流的形式进行渲染的,没有视频滚动条,所以就写了24h的时间组件 实现思路: 1.24h的时间刻度线总宽度为12960px2.点击24h线的某一点,获取这一点离左侧原点的距离(使用dom元素layerX和offsetLeft综合判断)3.计算点击时线段的占比比率4.每天的时间是86400000毫秒5.占比比率乘以86400000就是获取的你点击的时间 代码如下: <template>   <d

随机推荐