使用技术
vue + moment.js
最终效果
因为自身需求,所以周六周日被我隐藏了,周六周日的显示隐藏可以配置。
以下代码是按照有周六周日写的。
HTML 结构
此日历采用了ul li 的结构进行展示的,整个日历为一个ul,一行为一个li
<div>
<el-row type="flex" justify="space-between" align="middle" class="calendar-head">
<div class="month-btn" @click="prevFun">
<i class="el-icon-arrow-left"></i>
</div>
<div class="date-text">{{ monthShow }}</div>
<el-row type="flex" align="middle">
<!-- <div class="today-btn" @click="backToday">Today</div> -->
<div class="month-btn" @click="nextFun">
<i class="el-icon-arrow-right"></i>
</div>
</el-row>
</el-row>
<ul class="calendar-box">
<li class="week">
<div v-for="(item, index) in weekName" :key="index" class="calendar-week">
{{ item }}
</div>
</li>
<li v-for="(obj, index) in initDate" :key="index">
<div v-for="(item, i) in obj.week" :key="i" :style="current" :class="{ noshow: false }">
<span :class="item.class" @click="selectDate(item)">{{item.data}}</span>
</div>
</li>
</ul>
</div>
CSS样式
.calendar-head {
width: 90%;
margin: 0 auto;
}
.date-text {
font-size: 16px;
font-weight: 600;
}
.month-btn {
width: 32px;
height: 32px;
border-radius: 50%;
box-shadow: 0 0.25rem 0.75rem rgba(#003a7c, 0.16);
text-align: center;
line-height: 32px;
cursor: pointer;
}
.calendar-box .week {
display: flex;
justify-content: space-around;
margin-top: 24px;
}
.calendar-box .week div {
width: 13%;
height: 60px;
border-radius: 4px;
margin-bottom: 4px;
}
.calendar-box li div span {
display: inline-block;
position: relative;
height: 100%;
width: 100%;
border-radius: 4px;
}
.calendar-week {
text-align: center;
line-height: 50px;
}
.current {
background-color: #deecf9;
}
.prev,
.next {
background-color: #f7f7f7;
}
.noshow {
display: none;
}
日历计算逻辑
1、先了解下moment.js 库
它是专门用于日期处理的JavaScript库,对于日期格式化,日期计算是很方便。
安装方式: npm install moment --save
页面中直接使用: import moment from ‘moment’;
官网传送门:http://momentjs.cn/
本次日历用到的:
moment().daysInMonth(); // 获取当前月的天数
moment(‘11’).dayInMonth(); // 获取11月份的天数
moment().clone(); // 克隆moment,以防止影响原始的moment
moment().format(‘MM’); // 获取当前月份
moment().add(1, ‘month’) // 获取下一个月
moment().subtract(1,‘month’) // 获取上一个月
moment().startOf(‘month’).weekday(); //当前月第一天是星期几
2、 必须要知道的几个节点
- 当月的第一天是星期几
- 当月一共多少天 (是30 还是 31天)
3、代码
//一些变量声明
const initDate = ref([]);
const weekName = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
const monthShow = ref();
const today = moment();
// 当月多少天
const getMonthDays = (momentObj) => {
return momentObj.daysInMonth();
};
// 当月第一天是周几
const getWeekDays = (momentObj) => {
return momentObj.startOf('month').weekday();
};
// 日历日期
const setDate = (m) => {
const monthData = ref([]);
initDate.value = [];
const curDays = getMonthDays(m);
// 当月第一天是星期几
const curWeek = getWeekDays(m.clone());
// 定义一下上个月的总天数
const upDays = ref(getMonthDays(m.clone().subtract(1, 'month')));
const curMonth = m.format('MM');
const curYear = m.format('YYYY');
// 整个日历显示的天数 6行 一行七天 共42天
const calendarRow = ref(42);
//日历上显示下个月的日期
const nextFirstDate= ref(0);
for (const i = ref(0); i.value < calendarRow.value; i.value++) {
/** 此处是 i从0开始,小于当月第一天的星期数
例如: 当月第一天是周三,那么curWeek = 3,
那周三之前的天数(周日-周二)都是属于上个月的。
假如upDays = 30,即上个月共30天,
那28 29 30 这三天都要显示到日历上。
*/
if(i.value < curWeek) {
monthData.value.push({
data: upDays.value,
fullDate: '',
class: 'prev'
});
upDays.value--;
} else if (i.value >= curDays + curWeek) {
/**
大于当月总天数+当月第一天星期数,说明已经来到了下个月要显示的日期。
累加nextFirstDate变量,用于显示下个月的几个日期
例如: 当月31天,1号是星期三,来到这,
日历上已经显示了上个月的28 29 30号
以及当月的1-31号,一共31+3 = 34天了,当前日历显示42天
说明还有42-34= 8天要显示,这8天都是下个月的日期。
*/
nextFirstDate.value++;
monthData.value.push({
data: nextFirstDate.value,
fullDate: '',
class: 'next'
});
} else {
//当月要显示的日期
monthData.value.push({
data: i.value - curWeek + 1,
fullDate: '',
class: 'current'
});
monthShow.value = m.format('MMM YYYY')
}
}
// 以下是 用来处理日历数据结构的
const row = monthData.value.length / 7; // 计算日历有几行
for ( const i = ref<number>(0); i.value < row; i.value++ ) {
initDate.value.push({ week: [] });
}
const weekData = ref();
const count = ref(0);
const res = ref<any>([]);
for (const i = ref(0),len = monthData.value.length; i.value < len; i.value < len; i.value += 7) {
res.value.push(monthData.value.slice(i.value, i.value + 7));
res.value[0].forEach((item: any) => {
weekdata.value = moment(item.fullDate).format('dd');
initDate.value[count.value].week.push({ ...item, week: weekdata.value });
});
count.value += 1;
res.value = [];
}
};
// 切换上一月
const preFun = () => {
setDate(today.subtract(1, 'month'));
};
// 切换下一月
const nextFun = () => {
setDate(today.add(1,'month'))
};
onMounted(() => {
setDate(today);
})
4、说一下 日历的数据结构
这个日历的数据结构,是我自己根据需求来制定的。
结构如下:
最外层数据,几行就有几个对象
对象里面的week数组,一行有几个日期就有几个对象
const initDate = [
{
week:[
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'},
{data: 1, fulldate: '2021-12-12', class:'current'}
]
},
{},
{},
{},
{},
{}
]
简述
代码里用了vue3.0 + typeScript 的写法,如果你是要在vue2.0中使用的话,建议参照 逻辑部分自行修改成2.0的写法,(所有变量声明放入data中,const声明的函数放入methods 中,去掉声明时带有的 :any 类型声明就差不多了)