直接上代码
技术栈:Vue3
<script setup>
import { ref, reactive, onMounted, computed, nextTick } from 'vue';
const state = reactive({
year: '',
month: '',
date: '',
dataCount: [],
raw: {
year: '',
month: '',
date: '',
},
last: null,
next: null,
dataCard: [],
active: '',
tabActive: '',
});
const spanIcon1 = ref(null);
const spanIcon2 = ref(null);
const spanIcon3 = ref(null);
const month = ref(null);
const year = ref(null);
const holiday = ref(null);
const card = ref(null);
onMounted(() => {
getTime();
});
const isCurrentDate = computed(() => {
const { year, month, date } = state.raw;
return (v) => {
if (v.month == state.month) {
if (v.year == year && v.month == month && v.date == date) {
return 'active3';
}
} else {
//
if (v.month == month) {
return 'active1';
}
return 'active2';
}
};
});
// 判断当前是否是休息日
const isHoliday = computed(() => {
return (v) => {
let year = v.year;
let month = v.month - 1;
let date = v.date;
if (month == 0) {
console.log('进入');
year--;
}
const y = new Date(year, month, date).getDay();
if (y == 6 || y == 0) {
return 'isHoliday';
}
console.log(year, month - 1, date);
return '';
};
});
// 获取当前年月份
function getTime() {
const dt = new Date();
state.year = dt.getFullYear();
state.month = dt.getMonth() + 1;
state.date = dt.getDate();
//
state.raw.year = state.year;
state.raw.month = state.month;
state.raw.date = state.date;
getPush();
}
// 获取当月天数以及当月第一天星期
function getDayCounts(year, month) {
// 获取总天数
const total = new Date(year, month, 0).getDate();
// 获取第一天星期
const firstWeekDay = new Date(year, month - 1, 1).getDay();
return { total, firstWeekDay };
}
function getPush() {
// 获取当月的
const { total, firstWeekDay } = getDayCounts(state.year, state.month);
const month = [];
// 灌入当前月份的天数
for (let i = 1; i <= total; i++) {
month.push({ year: state.year, month: state.month, date: i });
}
// 获取上月的
const lastMonth = [state.year, state.month];
if (state.month == 1) {
lastMonth[0] = lastMonth[0] - 1;
lastMonth[1] = 12;
} else {
lastMonth[1] = lastMonth[1] - 1;
}
const { total: lastTotal } = getDayCounts(lastMonth[0], lastMonth[1]);
// 灌入上月份的
for (let i = lastTotal; i >= lastTotal - firstWeekDay + 2; i--) {
month.unshift({
year: lastMonth[0],
month: lastMonth[1],
date: i,
});
}
// 获取下个月的
const nextMonth = [state.year, state.month];
if (state.month == 12) {
nextMonth[0] = nextMonth[0] + 1;
nextMonth[1] = 1;
} else {
nextMonth[1] = nextMonth[1] + 1;
}
// console.log(42 - month.length);
// const { total: nextTotal } = getDayCounts(lastMonth[0], lastMonth[1]);
const length = month.length;
for (let i = 1; i <= 42 - length; i++) {
month.push({
year: nextMonth[0],
month: nextMonth[1],
date: i,
});
}
dateAnimation();
state.dataCount = month;
}
function dateAnimation() {
const last = createChildElementRectMap(document.querySelector('.box_body'));
nextTick(() => {
const next = createChildElementRectMap(document.querySelector('.box_body'));
last.forEach((v, k) => {
const tag = next.get(k);
const style = {
left: tag.left - v.left,
top: tag.top - v.top,
};
const keyframes = [
{ transform: `translate(${style.left}px,${style.top}px)` },
{ transform: `translate(0,0)` },
];
v.dom.animate(keyframes, {
duration: 400,
easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
});
});
});
}
function createChildElementRectMap(dom) {
const element = Array.from(dom.children);
return new Map(
element.map((item) => {
return [
item.getAttribute('data-index'),
{
left: item.getBoundingClientRect().left,
top: item.getBoundingClientRect().top,
dom: item,
},
];
})
);
}
// 切换月份
function changeMonth(val) {
if (val == 'prev') {
state.month--;
if (state.month == 0) {
state.month = 12;
state.year--;
}
return getPush();
}
state.month++;
if (state.month == 13) {
state.month = 1;
state.year++;
}
getPush();
}
// 返回今天
function toDay() {
state.year = state.raw.year;
state.month = state.raw.month;
state.date = state.raw.date;
getPush();
}
//让箭头旋转起来
function changeTime(v, dom, dom2) {
if (state.tabActive != v) {
state.tabActive = v;
const style = dom.getAttribute('style');
let deg = 180;
if (style) {
deg = Number(style.match(/[0-9]{3,}/)[0]) + 180;
}
dom.style.transform = `rotate(${deg}deg)`;
transitionFn(dom2);
cardData(v);
} else {
card.value.style.display = 'none';
state.tabActive = '';
}
}
// 数据
function cardData(v) {
switch (v) {
case 'year':
const map = new Array(70).fill(null).map((item, index) => {
return `${index + 1990}年`;
});
state.dataCard = map;
break;
case 'month':
const map1 = new Array(12)
.fill(null)
.map((item, index) => `${index + 1}月`);
state.dataCard = map1;
break;
default:
state.dataCard = [
'假期安排',
'元旦节',
'除夕',
'春节',
'清明节',
'劳动节',
'端午节',
'中秋节',
'国庆节',
];
break;
}
}
// 动画效果
function transitionFn(dom2) {
const last = card.value.offsetLeft;
card.value.style.left = dom2.offsetLeft + 'px';
card.value.style.display = 'block';
const keyframes = [
{
transform: `translate(${last - card.value.offsetLeft}px, 0px)`,
},
{ transform: `translate(0px, 0px)` },
];
card.value.animate(keyframes, {
duration: 400,
easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
});
window.addEventListener('click', clickFN, false);
}
function clickFN() {
if (state.tabActive) {
state.tabActive = '';
card.value.style.display = 'none';
window.removeEventListener('click', clickFN);
}
}
// 选择日期
function selectDate(item) {
state.active = item;
// 日期
const v = /[0-9]{1,}[年|月]$/.test(item);
if (v) {
card.value.style.display = 'none';
state.tabActive = '';
const date = item.split(/[月|年]/);
if (date[0].length <= 2) {
state.month = date[0];
return getPush();
}
state.year = date[0];
return getPush();
}
// 假期安排
}
</script>
<template>
<div class="container">
<div class="box">
<div class="box_main">
<div class="header">
<div
class="year"
ref="year"
@click.stop="changeTime('year', spanIcon1, year)"
>
<span>{{ state.year }}年</span>
<span ref="spanIcon1"
><el-icon><ArrowUp /></el-icon
></span>
</div>
<div
class="isMonth"
@click.stop="changeTime('month', spanIcon2, month)"
>
<div class="leftIcon" @click.stop="changeMonth('prev')"></div>
<div class="month" ref="month">
<span>{{ state.month }}月</span>
<span ref="spanIcon2"
><el-icon><ArrowUp /></el-icon
></span>
</div>
<div class="rightIcon" @click.stop="changeMonth('next')"></div>
</div>
<div
class="holiday"
ref="holiday"
@click.stop="changeTime('holiday', spanIcon3, holiday)"
>
<span>假期安排</span>
<span ref="spanIcon3"
><el-icon><ArrowUp /></el-icon
></span>
</div>
<div class="toDay" @click.stop="toDay">返回今天</div>
</div>
<div class="box_nav">
<div>一</div>
<div>二</div>
<div>三</div>
<div>四</div>
<div>五</div>
<div>六</div>
<div>日</div>
</div>
<div class="box_body">
<div
class="item"
v-for="(item, index) in state.dataCount"
:key="index"
:class="isCurrentDate(item)"
:data-index="`${item.date}`"
>
<span :class="isHoliday(item)">{{ item.date }}</span>
</div>
</div>
</div>
<div class="box_msg"></div>
<!-- -->
<div class="card" ref="card">
<el-scrollbar>
<ul>
<li
v-for="item in state.dataCard"
:key="item"
:class="{ active: state.active == item }"
@click="selectDate(item)"
>
{{ item }}
</li>
</ul>
</el-scrollbar>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
background-color: #edf1f4;
overflow: hidden;
user-select: none;
.box {
width: 592px;
height: 472px;
display: flex;
margin: 200px auto;
background-color: white;
border-radius: 16px;
position: relative;
.box_main {
flex-grow: 1;
border-radius: 16px 0 0 16px;
padding: 15px 15px 0;
border: 2px solid #4e6ef2;
border-right: none;
box-sizing: border-box;
.header {
height: 30px;
// background-color: red;
display: flex;
color: #333;
font-size: 13px;
.year {
width: 96px;
height: 30px;
margin-right: 9px;
border-radius: 6px;
border: 1px solid #d7d9e0;
box-sizing: border-box;
background: #ffffff;
user-select: none;
padding: 0 7px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
span {
transition: 0.4s;
}
}
.isMonth {
display: flex;
margin-right: 9px;
.leftIcon {
width: 30px;
height: 30px;
background: url(https://pss.bdstatic.com/r/www/cache/static/aladdin-san/app/ms_calendar/img/arrow_left_aaa9af6.png)
no-repeat center;
background-size: 16px auto;
cursor: pointer;
}
.rightIcon {
width: 30px;
height: 30px;
background: url(https://pss.bdstatic.com/r/www/cache/static/aladdin-san/app/ms_calendar/img/arrow_right_d3e2f09.png)
no-repeat center;
background-size: 16px auto;
cursor: pointer;
}
.month {
width: 80px;
height: 30px;
border-radius: 6px;
border: 1px solid #d7d9e0;
box-sizing: border-box;
background: #ffffff;
user-select: none;
padding: 0 7px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
span {
transition: 0.4s;
}
}
}
.holiday {
width: 96px;
height: 30px;
border-radius: 6px;
border: 1px solid #d7d9e0;
box-sizing: border-box;
background: #ffffff;
user-select: none;
margin-right: 30px;
padding: 0 7px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
span {
transition: 0.4s;
}
}
.toDay {
width: 68px;
height: 30px;
line-height: 30px;
text-align: center;
background: #f5f5f6;
border-radius: 6px;
color: #333;
cursor: pointer;
}
}
.box_nav {
height: 36px;
margin-top: 14px;
display: flex;
div {
width: 64px;
line-height: 36px;
text-align: center;
font-size: 13px;
color: #333333;
}
}
.box_body {
display: flex;
flex-wrap: wrap;
.item {
width: 60px;
height: 56px;
line-height: 56px;
border: 2px solid transparent;
text-align: center;
// box-sizing: border-box;
font-size: 18px;
cursor: pointer;
border-radius: 5px;
flex-shrink: 0;
.isHoliday {
color: #f73131;
}
}
.item:hover {
border: 2px solid #cc00ff;
}
.active1 {
color: #adadad;
}
.active2 {
color: #adadad;
}
.active2:hover {
border: 2px solid #e5e5e9;
}
.active1:hover {
border: 2px solid #bdbfc8;
}
.active3 {
border: 2px solid #4e6ef2;
}
}
}
.box_msg {
width: 112px;
height: 100%;
border-radius: 0 16px 16px 0;
background: #4e6ef2;
flex-shrink: 0;
}
.card {
position: absolute;
left: 20px;
width: 94px;
box-sizing: border-box;
box-shadow: 0 1px 10px 0 rgb(0 0 0 / 10%);
border-radius: 6px;
background: #ffffff;
top: 50px;
max-height: 312px;
display: none;
ul {
list-style: none;
margin: 0;
padding: 0;
text-align: center;
height: 100%;
li {
font-size: 13px;
padding: 7px 0;
cursor: pointer;
}
.active {
color: #315efb;
}
li:hover {
color: #315efb;
}
}
}
}
}
</style>
<style scoped>
.card:deep(.el-scrollbar__wrap) {
max-height: 312px;
overflow-x: hidden;
}
</style>
未完! 代码有BUG