实现vue时间选择DatePicker组件

实现vue时间选择DatePicker组件在日常工作中,比不可少会用到时间组件,我们的第一反应就是直接到组件库去找一下现成的来用下,毕竟,时间组件看起来还是很复杂的,对于没接触过的人来说,要自己去写一个这样的组件出来,还是有难度的

概述

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

前置知识

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

最终效果

动画.gif

布局方面

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

具体的实现思路

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

image.png 5.我们的目标就是生产上述的6*7=42的数据。

具体实现

目录结构

image.png

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;
}

  • 几个核心工具函数演示,演示下genarateDayData函数最终根据日期生成的数据结构
    • 生成的数据是6*7的二维数组,对应时间组件的表格行和列

image.png

动画.gif

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组件分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18966.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注