详解怎么使用vue封装一个自定义日历组件
怎么开发一个自定义日历的vue组件,下面本篇文章就手把手教你如何封装一个自定义日历组件,希望对大家有所帮助!
众所周知啊,一般来说,如果项目中有需要用到日历组件,往往是找第三方UI库中的组件来使用,或者是找现成的其他第三方插件。对于很多小伙伴来说,第一眼看到日历组件,下意识的就会觉得很复杂,无从下手。但是当我阅读了这个日历插件的源码之后,发现并没有我想象中的复杂。我以前傻傻得认为,想要做一个日历组件,得需要把距离现在年份前后至少十年的日历数据都获取到,然后才能进行下一步的开发。
然而,在我尝试着阅读了dycalendar.js这个库的源码之后,一方面感觉自己太笨了,把问题想得太复杂了。另外也感慨作者思路之清晰。看完之后感觉受益匪浅。
在将作者的思路逻辑梳理完毕后,我依据这个思路开发了一个vue组件。如下图所示:
接下来,就随着我一起看看如何开发一个自己的日历组件吧。
核心代码实现
1、梳理思路
获取到目标日期数据
获取到当前日期的各项重要属性,诸如当前年
,当前月
,当前日期
,当前星期几
,当前月一共有几天
,当前月的第一天对应的是星期几
,上个月总共有多少天
等。
根据这些属性,来生成具体的日历日期数据列表
,然后将其循环渲染到模板中。
当切换月份的时候,获取到新的目标日期对应的各项关键数据。vue检测到日历属性变化之后,通知页面进行更新。
2、初始化所需要的数据
一般来说,成熟的日历组件,日期都是一个双向绑定的变量。为了方便使用,我们也采用双向绑定的方式。
- <script setup>
- import { reactive, ref, computed, watch } from "vue";
- const props = defineProps({
- modelValue: Date,
- });
- const emits = defineEmits(["update:modelValue"]);
- /**
- * 最小年份
- */
- const MIN_YEAR = 1900;
- /**
- * 最大年份
- */
- const MAX_YEAR = 9999;
- /**
- * 目标日期
- */
- const targetDate = ref(props.modelValue);
接下来,我们还需要初始化一些常量用来表示月份和日期:
- /**
- * 有关月度的名称列表
- */
- const monthNameList = {
- chineseFullName: [
- "一月",
- "二月",
- "三月",
- "四月",
- "五月",
- "六月",
- "七月",
- "八月",
- "九月",
- "十月",
- "十一月",
- "十二月",
- ],
- fullName: [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December",
- ],
- mmm: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ],
- };
- /**
- * 有关周几的名称列表
- */
- const dayNameList = [
- {
- chineseFullName: "周日",
- chineseShortName: "日",
- fullName: "Sunday",
- shortName: "Sun",
- dayNumber: 0,
- },
- {
- chineseFullName: "周一",
- chineseShortName: "一",
- fullName: "Monday",
- shortName: "Mon",
- dayNumber: 1,
- },
- {
- chineseFullName: "周二",
- chineseShortName: "二",
- fullName: "Tuesday",
- shortName: "Tue",
- dayNumber: 2,
- },
- {
- chineseFullName: "周三",
- chineseShortName: "三",
- fullName: "Wednesday",
- shortName: "Wed",
- dayNumber: 3,
- },
- {
- chineseFullName: "周四",
- chineseShortName: "四",
- fullName: "Thursday",
- shortName: "Thu",
- dayNumber: 4,
- },
- {
- chineseFullName: "周五",
- chineseShortName: "五",
- fullName: "Friday",
- shortName: "Fri",
- dayNumber: 5,
- },
- {
- chineseFullName: "周六",
- chineseShortName: "六",
- fullName: "Saturday",
- shortName: "Sat",
- dayNumber: 6,
- },
- ];
接下来,准备几个vue的响应式数据:
- /**
- * 今日
- */
- const today = new Date();
- /**
- * 日历的各项属性
- */
- const calendarProps = reactive({
- target: {
- year: null,
- month: null,
- date: null,
- day: null,
- monthShortName: null,
- monthFullName: null,
- monthChineseFullName: null,
- firstDay: null,
- firstDayIndex: null,
- totalDays: null,
- },
- previous: {
- totalDays: null,
- },
- });
- /**
- * 用于展现的日历数据
- */
- const calendarData = ref([]);
3、初始化日历的各项属性
接下来,通过setCalendarProps
方法获取日历的各个属性,逐个填充calendarProps
中的数据:
- function setCalendarProps() {
- if (!targetDate.value) {
- targetDate.value = today;
- }
- // 获取目标日期的年月日星期几数据
- calendarProps.target.year = targetDate.value.getFullYear();
- calendarProps.target.month = targetDate.value.getMonth();
- calendarProps.target.date = targetDate.value.getDate();
- calendarProps.target.day = targetDate.value.getDay();
- if (
- calendarProps.target.year < MIN_YEAR ||
- calendarProps.target.year > MAX_YEAR
- ) {
- console.error("无效的年份,请检查传入的数据是否是正常");
- return;
- }
- // 获取到目标日期的月份【中文】名称
- let dateString;
- dateString = targetDate.value.toString().split(" ");
- calendarProps.target.monthShortName = dateString[1];
- calendarProps.target.monthFullName =
- monthNameList.fullName[calendarProps.target.month];
- calendarProps.target.monthChineseFullName =
- monthNameList.chineseFullName[calendarProps.target.month];
- // 获取目标月份的第一天是星期几,和在星期几中的索引值
- const targetMonthFirstDay = new Date(
- calendarProps.target.year,
- calendarProps.target.month,
- 1
- );
- calendarProps.target.firstDay = targetMonthFirstDay.getDay();
- calendarProps.target.firstDayIndex = dayNameList.findIndex(
- (day) => day.dayNumber === calendarProps.target.firstDay
- );
- // 获取目标月份总共多少天
- const targetMonthLastDay = new Date(
- calendarProps.target.year,
- calendarProps.target.month + 1,
- 0
- );
- calendarProps.target.totalDays = targetMonthLastDay.getDate();
- // 获取目标月份的上个月总共多少天
- const previousMonth = new Date(
- calendarProps.target.year,
- calendarProps.target.month,
- 0
- );
- calendarProps.previous.totalDays = previousMonth.getDate();
- }
需要注意的一个知识点是,在获取本月多少天和上个月多少天的时候,都将date值设置为了
0
。这是因为当date值为0
的时候,返回的Date对象是上个月的最后一天。所以说,为了获取本月多少天,需要将本月的month值加1
。
执行这个方法之后,此时calendarProps
的值为:
4、根据日历属性生成日历日期的数据
当我们已经知道本月第一天对应的周几索引值、本月一共有多少天和上个月一共有多少天这三个核心数据之后,就可以开始生成对应的日历数据了。
思路如下:
由于大部分情况下,本月的第一天不是从头开始的,之前的部分是上个月的日期。所以第一行要单独进行处理。
设置一个公用的date数值,初始值设置为1
。然后从本月第一天对应的周几索引值开始进行递增。本月之前的日期和之后的日期设置一个算法进行计算。
为了方便之后进行日期切换、样式区分,将生成的数据加工成一个对象,其中包含日期类型——dateType
,表示是本月还是上月还是下月;
- /**
- * 生成日历的数据
- */
- function setCalendarData() {
- let i;
- let date = 1;
- const originData = [];
- const firstRow = [];
- // 设置第一行数据
- for (i = 0; i <= 6; i++) {
- // 设置目标月份之前月份的日期数据
- if (i < calendarProps.target.firstDayIndex) {
- const previousDate =
- calendarProps.previous.totalDays -
- calendarProps.target.firstDayIndex +
- (i + 1);
- firstRow.push({
- dateObj: new Date(
- calendarProps.target.year,
- calendarProps.target.month - 1,
- previousDate
- ),
- dateNumber: previousDate,
- dateType: "previous"
- });
- } else {
- // 设置目标月份当月的日期数据
- firstRow.push({
- dateObj: new Date(
- calendarProps.target.year,
- calendarProps.target.month,
- date
- ),
- dateNumber: date,
- dateType: "current"
- });
- date++;
- }
- }
- originData.push(firstRow);
- // 设置后面五行的数据
- for (let j = 0; j <= 4; j++) {
- const rowData = [];
- for (let k = 0; k <= 6; k++) {
- // 设置目标月份剩下的日期数据
- if (date <= calendarProps.target.totalDays) {
- rowData.push({
- dateObj: new Date(
- calendarProps.target.year,
- calendarProps.target.month,
- date
- ),
- dateNumber: date,
- dateType: "current"
- });
- } else {
- // 设置目标月份下个月的日期数据
- const nextDate = date - calendarProps.target.totalDays;
- rowData.push({
- dateObj: new Date(
- calendarProps.target.year,
- calendarProps.target.month + 1,
- nextDate
- ),
- dateNumber: nextDate,
- dateType: "next"
- });
- }
- date++;
- }
- originData.push(rowData);
- }
- calendarData.value = originData;
- }
至此,这个日历组件的核心部分的逻辑就已经实现了。你看,是不是很简单?
接下来,我们只需要根据calendarData
中的数据渲染出相应的html模板和添加上样式就可以了。
5、添加模板和样式部分
一般来说,日历组件都是网格状的结构,所以我选择table的方式进行渲染。不过你要是问我还有没有别的方式,那还是有的,比如使用flex布局或者grid布局,但是如果采用这种方式的话,calendarData
的数据结构就不是现在这个样子了。
dom结构如下图:
至于按钮边框的流动效果,是我参照苏苏的文章做的,详情请见:
Clip-path实现按钮流动边框动画 juejin.cn/post/719877…
然后剩下的样式部分,即兴发挥或者根据UI设计图绘制即可。想必各位都领教过UI姐姐们精美的设计图吧(嘻嘻
具体的代码部分就不贴在文章中了,如有需要可以直接查看下方的完整源码
结语
有些感觉很麻烦的组件,可能核心逻辑往往不是那么复杂。有些时候,可能仅仅是需要一些耐心,将代码一行一行的拆解出来阅读,理清楚其中的思路。
本文网址:https://www.zztuku.com/detail-14045.html
站长图库 - 详解怎么使用vue封装一个自定义日历组件
申明:本文转载于《掘金社区》,如有侵犯,请 联系我们 删除。
您还没有登录,请 登录 后发表评论!
提示:请勿发布广告垃圾评论,否则封号处理!!