最近项目的需求是这样的,有开始日期和结束日期的限制,同时根据一个首次约定日期以及一个周期(月、季度、半年、一年),自动生成一条或者多条数据的列表,其中列表的约定日期规则如下:
① 周期为月:根据首次约定日期,每月固定日期生成一条数据
② 周期为季度:根据首次约定日期,每三个月固定日期生成一条数据
③ 周期为半年:根据首次约定日期,每六个月固定日期生成一条数据
④ 周期为年:根据首次约定日期,每年固定日期生成一条数据
⑤ 特殊情况:当首次约定日期设置为29/30/31日时,当某月份没有29、30或者31日时,则取该月份最后一天为列表的约定日期
在做这个需求的时候,遇到过一些坑,现在记录一下,给自己加深印象也给各位大佬看看,欢迎提出问题一起探讨~
/**
* 计算生成表格的条数
*/
calcTableData() {
// this.startDate, this.endDate: 限制的开始和结束日期
// this.period: 周期(月/季度/半年/年), this.appointDate: 首次约定日期
if (this.startDate && this.endDate && this.period && this.appointDate) {
let dateStart = moment(this.startDate).format("YYYY/MM/DD");
let dateEnd = moment(this.endDate).format("YYYY/MM/DD");
// 计算开始日期和结束日期相差几个月
const dateMinus = (d1: any, d2: any) => {
let m1 = parseInt(d1.split("/")[1].replace(/^0+/, "")) + parseInt(d1.split("/")[0]) * 12;
let m2 = parseInt(d2.split("/")[1].replace(/^0+/, "")) + parseInt(d2.split("/")[0]) * 12;
let dateDiff = 0;
// 当结束的日期比开始日期的日期(几号)大时, 月份+1
if (new Date(dateEnd).getDate() > new Date(dateStart).getDate()) {
dateDiff = 1;
}
let diff = m2 - m1 + dateDiff;
return diff;
}
const diff = dateMinus(dateStart, dateEnd);
// 此处为方便计算, this.period按月份基数来, 即周期为月时period为1个月, 周期为季度时period为3个月, 周期为半年时period为6个月...
switch (this.period) {
// 月
case '1':
this.handleTableData(diff, this.period);
break;
// 季度
case '3':
this.handleTableData(Math.ceil(diff / 3), this.period);
break;
// 半年
case '6':
this.handleTableData(Math.ceil(diff / 6), this.period);
break;
// 一年
default:
this.handleTableData(Math.ceil(diff / 12), this.period);
}
}
}
handleTableData(count: number, type: string) {
this.tableData = [];
for (let i = 0; i < count; i++) {
let obj = {
'其他属性': '',
// 当列表数据为第一条时, 直接取首次约定日期
appointDate: i === 0 ? this.appointDate : this.hanldeDate(i, type),
key: i,
}
this.tableData.push(obj);
}
}
关键日期算法及思路:
这里取28号是因为平年时, 2月份最多28天。
hanldeDate(i: number, type: string) {
// baseDate为列表上一条数据的约定日期
let baseDate = new Date(this.tableData[i - 1].planDate);
let appointDate = baseDate.setMonth(baseDate.getMonth() + Number(type)) as any;
appointDate = moment(appointDate);
/**
* 首先获取首次约定日期的日期 day是几号
* 如果day小于等于28, 直接setMonth的参数为baseDate的月份即可。
* 如果day大于28的情况下:
* 计算appointDate减去5天, 求出appointDate这个月的最后一天是几号为endDate,
* 如果appointDate这个月的最后一天大于day, 那么setMonth只需设置月份
* 如果appointDate这个月的最后一天小于day, 那么setMonth需要设置2个参数, 参数为:月份和该月最后一天的日期endDate
* 例:
* 如果首次约定日期为2021/8/28, 求出day为28, 此时setMonth(baseDate.getMonth())即可
* 如果首次约定日期为2021/8/31, 求出day为31, 当周期为按月时, 直接setMonth(baseDate.getMonth())会有bug, 因为9月没有30号, 此时appointDate会变成10月1号,
* 因此可将appointDate减去5天, 再计算出9月的最后一天endDate为30号, 由于endDate小于day, appointDate的结果为setMonth(baseDate.getMonth(), endDate)。
*/
let day = (new Date(moment(this.appointDate).format("YYYY-MM-DD"))).getDate();
if (day > 28) {
let dateCopy = (new Date(appointDate)).getTime() - 5 * 24 * 60 * 60 * 1000;
let endDateCopy = getEndDate(new Date(dateCopy)) as any;
let endDate = new Date(endDateCopy.endDate).getDate();
if (endDate < day) {
// 此处需要减1, 因为baseDate经过上面的设置后, 此时的baseDate已经为下一个月的1号了
appointDate = baseDate.setMonth(baseDate.getMonth() - 1, endDate) as any;
} else {
appointDate = baseDate.setMonth(baseDate.getMonth(), day) as any;
}
appointDate = moment(appointDate)
}
return appointDate;
}
getEndDate函数在此,哈哈
getEndDate函数来源于网络… 不好意思,实在是忘记出处了。
export function getEndDate(date: any) {
let nowDate = date;
let cloneNowDate = date;
let fullYear = nowDate.getFullYear();
let month = nowDate.getMonth() + 1; // getMonth 方法返回 0-11,代表1-12月
let endOfMonth = new Date(fullYear, month, 0).getDate(); // 获取本月最后一天
function getFullDate(targetDate: any) {
let D, y, m, d;
if (targetDate) {
D = new Date(targetDate);
y = D.getFullYear();
m = D.getMonth() + 1;
d = D.getDate();
} else {
y = fullYear;
m = month;
d = date;
}
m = m > 9 ? m : '0' + m;
d = d > 9 ? d : '0' + d;
return y + '-' + m + '-' + d;
}
let endDate = getFullDate(cloneNowDate.setDate(endOfMonth));//当月最后一天
let starDate = getFullDate(cloneNowDate.setDate(1));//当月第一天
return { endDate, starDate }
}