首先看下需求页面的整体布局。
页面分为上下布局,上边模块包含左侧日历和右侧导入部分,下边模块是数据状态部分。日历和导入组件固定高度420px;日历宽度500px;数据状态宽度100%,高度自适应。
项目是由vue2+elementui开发,这里主要说的是日历的高度如何动态设置?
由于业务需求,日历只展示当前月份数据,使用css将上个月份和下个月份数据进行隐藏,所以日历有时是5行数据展示,有时是6行数据展示,但是总的高度(420px)不变,所以每个格子高度就需要根据行数动态进行设置。
要动态设置,必须做到两点:
- 要知道当前月份所占行数
- 如何计算高度
先来看看第一点,通过观察发现,日历组件是用table开发的,而不管是切换到哪个月份,table下固定的是展示6个tr,第一个tr下包括上个月和当前月分数据或者只有当前月数据,第二、三、四个tr都是当前月份数据,最后一行是当前月份和下个月数据或者下个月数据,所以我们可以循环遍历所有的tr,统计所有的tr数量,如果tr下所有的td类名是next,则不统计当前tr,就可以得到当前月份的行数。
getRows() {
this.$nextTick(() => {
// 获取<tbody>元素
const tbody = document.querySelector("tbody");
// 初始化计数器
let trCount = 0;
// 遍历所有<tr>
tbody.querySelectorAll("tr").forEach((tr) => {
// 检查所有<td>是否都有next类
const allNext = Array.from(tr.querySelectorAll("td")).every((td) =>
td.classList.contains("next")
);
// 如果没有allNext(即至少有一个<td>不是next类),则计数器加一
if (!allNext) {
trCount++;
}
});
document.documentElement.style.setProperty("--tr-rows-number", trCount);
});
拿到了行数,现在需要去动态计算了,我们可以使用
document.documentElement.style.setProperty("--tr-rows-number", trCount);
设置了一个变量,将计算的行数进行赋值。然后再css中使用当前变量即可。
// 设置日历
::v-deep .el-calendar-table .el-calendar-day {
/* 此处的288是用总高度 - 顶部一个时间选择器高度 - 星期几的高度 - tr中的border */
height: calc(288px / var(--tr-rows-number));
padding: 0;
color: #fff;
}
看下全部代码:
<!--
* @Author: rk
* @Description: 日历组件
* @Date: 2024-05-13 09:27:24
* @LastEditors: rk
* @LastEditTime: 2024-05-29 09:45:10
-->
<template>
<el-card v-loading="loading" shadow="never">
<div class="calendar-box">
<div>
<el-date-picker
v-model="currentDate"
type="month"
size="small"
style="width: 120px"
placeholder="选中年月"
value-format="yyyy-MM"
:clearable="false"
@change="handleMonthChange"
/>
</div>
<el-button v-if="isSameDay()" size="small" plain @click="goBackToday">
返回今天
</el-button>
</div>
<el-calendar ref="calendar" v-model="calendarDate">
<template slot="dateCell" slot-scope="{ data }">
<div
class="calendar-day"
v-for="(item, index) in statusData(data.day)"
:key="index"
:class="['red', 'green'][item.status]"
@click="handleDateChange(data)"
>
<i
v-if="data.isSelected"
class="icon-select el-icon-circle-check"
></i>
{{ data.day.split("-").slice(2).join("-") }}
</div>
</template>
</el-calendar>
</el-card>
</template>
<script>
// utils
import { timeFormate } from "js-fastcode";
export default {
name: "",
props: {
// 接口
apiName: {
type: Function,
default: () => {},
},
},
data() {
return {
loading: false,
// 当前日期
currentDate: timeFormate(1),
// 日历时间
calendarDate: new Date(),
// 日历数据
calendarData: [],
};
},
created() {
// 获取日历数据占据行数
this.getRows();
this.$emit("get-date-result", timeFormate(2));
},
methods: {
// 获取日历数据
getCalendarData() {
this.loading = true;
let params = { yearMonth: this.currentDate };
this.apiName(params).then((res) => {
this.loading = false;
this.calendarData = [
{ date: "2024-05-01", status: 1 },
{ date: "2024-05-02", status: 1 },
{ date: "2024-05-03", status: 1 },
{ date: "2024-05-04", status: 1 },
{ date: "2024-05-05", status: 1 },
{ date: "2024-05-06", status: 1 },
{ date: "2024-05-07", status: 1 },
{ date: "2024-05-08", status: 1 },
{ date: "2024-05-09", status: 1 },
{ date: "2024-05-10", status: 1 },
{ date: "2024-05-11", status: 1 },
{ date: "2024-05-12", status: 1 },
{ date: "2024-05-13", status: 1 },
{ date: "2024-05-14", status: 1 },
{ date: "2024-05-15", status: 1 },
{ date: "2024-05-16", status: 1 },
{ date: "2024-05-17", status: 1 },
{ date: "2024-05-18", status: 1 },
{ date: "2024-05-19", status: 1 },
{ date: "2024-05-20", status: 1 },
{ date: "2024-05-21", status: 1 },
{ date: "2024-05-22", status: 1 },
{ date: "2024-05-23", status: 1 },
{ date: "2024-05-24", status: 1 },
{ date: "2024-05-25", status: 1 },
{ date: "2024-05-26", status: 1 },
{ date: "2024-05-27", status: 0 },
{ date: "2024-05-28", status: 0 },
{ date: "2024-05-29", status: 0 },
{ date: "2024-05-30", status: 1 },
{ date: "2024-05-31", status: 1 },
];
});
},
// 切换年月
handleMonthChange(val) {
// 获取当前时间月份
let month = timeFormate(1);
// 如果时间一致,则默认当前日期
if (val === month) {
this.calendarDate = new Date();
this.$emit("get-date-result", timeFormate(2));
} else {
// 否则默认每个月第一天
this.calendarDate = new Date(val);
this.$emit("get-date-result", val + "-01");
}
this.getRows();
this.getCalendarData();
},
// 返回今天
goBackToday() {
this.currentDate = timeFormate(1);
this.calendarDate = new Date();
this.getRows();
this.getCalendarData();
this.$emit("get-date-result", timeFormate(2));
},
// 判断是否是当前日期
isSameDay() {
// 日历时间
const date1 = timeFormate(2, new Date(this.calendarDate));
// 当前时间
const date2 = timeFormate(2, new Date());
return date1 !== date2;
},
// 过滤出当前数据的状态
statusData(date) {
return this.calendarData.filter((item) => {
return date === item.date;
});
},
// 切换日期
handleDateChange(data) {
this.calendarDate = new Date(data.day);
this.$emit("get-date-result", data.day);
},
/**
* 因为每个月日历的行数不一样,所以需要动态计算出日历中需要显示的行数
*/
getRows() {
this.$nextTick(() => {
// 获取<tbody>元素
const tbody = document.querySelector("tbody");
// 初始化计数器
let trCount = 0;
// 遍历所有<tr>
tbody.querySelectorAll("tr").forEach((tr) => {
// 检查所有<td>是否都有next类
const allNext = Array.from(tr.querySelectorAll("td")).every((td) =>
td.classList.contains("next")
);
// 如果没有allNext(即至少有一个<td>不是next类),则计数器加一
if (!allNext) {
trCount++;
}
});
document.documentElement.style.setProperty("--tr-rows-number", trCount);
});
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-calendar__body {
padding: 0 !important;
}
// 隐藏日历中上个月的数据
::v-deep .el-calendar-table:not(.is-range) td.next {
display: none;
}
// 隐藏日历中下个月的数据
::v-deep .el-calendar-table:not(.is-range) td.prev {
visibility: hidden;
}
// 设置日历头部样式
::v-deep .el-calendar-table thead th {
text-align: center;
font-size: 14px;
}
::v-deep .el-calendar__header {
padding: 0;
display: none;
}
// 设置日历
::v-deep .el-calendar-table .el-calendar-day {
height: calc(288px / var(--tr-rows-number));
padding: 0;
color: #fff;
}
// card样式设置
::v-deep .el-card__body {
padding: 6px 20px 20px;
}
// 日历头部样式自定义
.calendar-box {
height: 55px;
line-height: 55px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid $border-color-card;
}
.calendar-day {
width: 100%;
height: 100%;
font-size: 12px;
text-align: center;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.icon-select {
right: 0;
top: 0;
position: absolute;
font-size: 20px;
color: #fff;
}
}
// 有数据状态
.green {
background: $color-success;
}
// 无数据状态
.red {
background: $color-danger;
}
</style>
效果图:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。