一个Vue可滑动calendar日历组件

先看下效果图

组件概述

日历组件将支持以下功能:

  • 显示当前月份的日历。

  • 通过滑动切换到上一个月或下一个月

  • 点击特定日期可以触发事件。

使用方式

组件中使用moment和vant的swipe组件需要安装

npm i moment vant

main.js引入下

import { Swipe, SwipeItem } from 'vant';

Vue.use(Swipe);

Vue.use(SwipeItem);

组件也发布了npm包可直接使用

npm i vue-swipe-calendar

组件结构

ini 复制代码
<template>
  <div class="calendar">
    <div class="calendar-header">
      <div>{{ getFormatDate() }}</div>
      <div>
        <span class="btn" @click="handleMonth('pre')">上</span>
        <span class="btn" @click="handleMonth('current')">今</span>
        <span class="btn" @click="handleMonth('next')">下</span>
      </div>
    </div>
    <van-swipe ref="vanSwipe" :show-indicators="false" @change="onSwipeChange">
      <van-swipe-item v-for="dateArr in dateArrs" :key="dateArr.fullDate">
        <div class="calendar-week">
          <div
            class="calendar-week-item flex-c"
            v-for="(item, index) in weekArrs"
            :key="index"
          >
            {{ item }}
          </div>
        </div>
        <div class="calendar-date">
          <div
            class="calendar-date-item flex-c"
            v-for="item in dateArr"
            :key="item.fullDate"
            :class="item.type"
            @click="handleDate(item)"
          >
            {{ item.date }}
            <slot name="calendarItem" :item="item"></slot>
          </div>
        </div>
      </van-swipe-item>
    </van-swipe>
  </div>
</template>

<script>
import moment from "moment";
export default {
  name: "SwipeCalendar",
  data() {
    return {
      weekArrs: ["一", "二", "三", "四", "五", "六", "日"],
      dateArrs: [],
      today: moment().format("YYYY-MM-DD"),
      currentMonth: "",
      allCell: 42,
      currentSwipeIndex: 0, // 当前轮播的索引
    };
  },
  components: {},
  mounted() {
    this.initCalendar("current", 0);
  },
  methods: {
    onSwipeChange(index) {
      this.dateArrs = [[], [], []];
      // 判断滑动方向
      if (
        index - this.currentSwipeIndex === 1 ||
        index - this.currentSwipeIndex === -2
      ) {
        //console.log('向左滑动++');
        this.initCalendar("next", index);
      } else {
        //console.log('向右滑动--');
        this.initCalendar("pre", index);
      }

      this.currentSwipeIndex = index;
    },
    handleMonth(type) {
      if (type == "pre") {
        this.$refs.vanSwipe.prev();
      } else if (type == "next") {
        this.$refs.vanSwipe.next();
      } else {
        this.initCalendar("current");
      }
    },
    initCalendar(type, index) {
      if (type == "pre") {
        this.currentMonth = moment(this.currentMonth)
          .subtract(1, "month")
          .startOf("month");
      } else if (type == "next") {
        this.currentMonth = this.currentMonth.add(1, "month").startOf("month");
      } else {
        this.currentMonth = moment(this.today).startOf("month");
      }
      //获取当前上个月的第一天
      const preMonthFirstDay = this.currentMonth
        .clone()
        .subtract(1, "month")
        .startOf("month");
      //获取当前下个月的第一天
      const nextMonthFirstDay = this.currentMonth
        .clone()
        .add(1, "month")
        .startOf("month");
      const currentDateArrs = this.getMonthDateArrs(this.currentMonth.clone());
      const nextDateArrs = this.getMonthDateArrs(nextMonthFirstDay);
      const preDateArrs = this.getMonthDateArrs(preMonthFirstDay);
      if (index === 0) {
        // 初始化第0张图 当前月在第0张 月视图数据顺序为120(1为当前月)
        this.dateArrs = [currentDateArrs, nextDateArrs, preDateArrs];
      } else if (index === 1) {
        // 划到第1张图 当前月在第1张 月视图数据顺序为012(1为当前月)
        this.dateArrs = [preDateArrs, currentDateArrs, nextDateArrs];
      } else if (index === 2) {
        //划到第2张图 当前月在第2张 月视图数据顺序为201(1为当前月)
        this.dateArrs = [nextDateArrs, preDateArrs, currentDateArrs];
      }
    },
    getFormatDate() {
      if (this.currentMonth) {
        return this.currentMonth.format(`YYYY年M月`);
      } else {
        return moment(this.today).format(`YYYY年M月`);
      }
    },
    getMonthDateArrs(firstDay) {
      // 获取当前月一共多少天(占格子)
      const currentCell = this.getMonthDates(firstDay);
      // 获取当前月之前多少天(占格子)
      const preCell = this.getPreMonthDates(firstDay);
      // 获取当前月之后多少天(占格子)
      const nextCell = this.getNextMonthDates(firstDay, currentCell, preCell);
      return [...preCell, ...currentCell, ...nextCell];
    },
    getDateObj(day, diff, type) {
      const currentDate = moment(day).subtract(diff, "days");
      const fullDate = currentDate.format("YYYY-MM-DD"); // 完整日期格式
      const date = currentDate.date(); // 日期部分
      return { fullDate, date, type };
    },
    getMonthDates(firstDay) {
      // 计算当前月份补充的天数
      let daysInMonth = moment(firstDay).daysInMonth() - 1;
      const lastDay = moment(firstDay).endOf("month");
      const dates = [];
      // 遍历当前月的每一天
      while (daysInMonth >= 0) {
        const daysInfo = this.getDateObj(lastDay, daysInMonth, "current");
        dates.push(daysInfo);
        daysInMonth--; // 移动到下一天
      }
      return dates;
    },
    getPreMonthDates(firstDay) {
      //  计算前面需要补充的天数 获取当前月第一天是周几(0 表示周日,1 表示周一,...,6 表示周六)
      let preDaysInMonth = moment(firstDay).day() - 1;
      // 将周日0改为6
      if (preDaysInMonth === -1) {
        preDaysInMonth = 6;
      }
      const dates = [];
      while (preDaysInMonth > 0) {
        const daysInfo = this.getDateObj(firstDay, preDaysInMonth, "pre");
        dates.push(daysInfo);
        preDaysInMonth--;
      }
      return dates;
    },
    getNextMonthDates(firstDay, currentCell, preCell) {
      //计算当前月份之后补充的天数
      let nextDaysInMonth = this.allCell - currentCell.length - preCell.length;
      const lastDay = firstDay.endOf("month");
      const cellLastDay = lastDay.add(nextDaysInMonth, "days");
      const dates = [];
      while (nextDaysInMonth > 0) {
        const daysInfo = this.getDateObj(
          cellLastDay,
          nextDaysInMonth - 1,
          "next"
        );
        dates.push(daysInfo);
        nextDaysInMonth--;
      }
      return dates;
    },
    handleDate(item) {
      this.$emit("handleDate", item);
    },
  },
};
</script>
  <style lang="less" scoped>
  .calendar {
    padding: 20px;

    .flex-c {
      display: flex;
      justify-content: center;
      align-items: center;
    }

    &-header {
      padding: 0 10px;
      display: flex;
      justify-content: space-between;
      align-items: center;

      .btn {
        margin-left: 10px;
      }
    }

    &-week {
      display: flex;

      &-item {
        flex: 1;
        height: 30px;
      }
    }

    /deep/.van-swipe {
      overflow: hidden;

      .van-swipe__track {
        display: flex;
      }
    }

    .calendar-date {
      display: flex;
      flex-wrap: wrap;

      .calendar-date-item {
        width: calc(100% / 7);
        height: 40px;
        color: #333;

        &.pre,
        &.next {
          color: #999;
        }
      }
    }
  }
  </style>
相关推荐
华仔啊6 小时前
前端发版总被用户说“没更新”?一文搞懂浏览器缓存,彻底解决!
前端·javascript
赶紧提桶跑路了6 小时前
基于WebRTC实现音视频通话
前端
Y_时光机_Y6 小时前
JNI 常见异常分析
前端
前端fighter6 小时前
Express vs Koa vs Egg.js:Node.js 后端框架选型指南
前端·后端·面试
龙在天7 小时前
如何做虚拟滚动列表缓冲区?流畅又不出现白屏
前端
跟橙姐学代码7 小时前
PyInstaller打包避坑全攻略:新手一看就会,老手也能涨姿势
前端·python·ipython
FanetheDivine7 小时前
在react中处理输入法合成问题
前端·react.js
yinuo7 小时前
Uni-App跨端开发实战:编译H5跳转全平台终极指南(03)
前端
天蓝色的鱼鱼7 小时前
为什么 Vite 选择 Rolldown?一次关于性能、生态与未来的深度权衡
前端·vite