1
头图

首先看下需求页面的整体布局。

页面分为上下布局,上边模块包含左侧日历和右侧导入部分,下边模块是数据状态部分。日历和导入组件固定高度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>

效果图:


前端搬运工
740 声望66 粉丝

一杯茶,一根烟,一行代码写一天。