首先看下需求頁面的整體佈局。
頁面分為上下佈局,上邊模塊包含左側日曆和右側導入部分,下邊模塊是數據狀態部分。日曆和導入組件固定高度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>
效果圖: