在现代 Web应用开发中,日历组件是一个非常常见的功能模块。无论是日程管理、预约系统还是活动安排,都离不开一个直观易用的日历界面。本文将介绍如何使用Vue3 结合 FullCalendar 和农历库,打造一个既支持公历又能显示农历、节气和节日的多功能日历组件。

1. 实现效果

  • 支持多月份年度视图展示
  • 同时显示公历日期和农历信息
  • 自动标记传统节日和法定节假日
  • 显示二十四节气
  • 支持年份切换和快速回到当前年份
  • 节假日特殊样式显示

日历效果

2. 核心技术栈

  • Vue3 + Vite:前端框架和构建工具
  • FullCalendar:功能强大的 JavaScript 日历
  • @fullcalendar/multimonth:FullCalendar 的多月份视图插件
  • lunar-javascript:农历转换库,用于获取农历日期、节气和节日
  • Element Plus:提供选择器组件
  • Less:CSS预处理器,用于样式定制

3. 功能实现详解
3.1 环境准备与依赖安装
fullcalendar官网: https://fullcalendar.io/
element-plus官网:https://element-plus.org/

npm install @fullcalendar/core 
npm install @fullcalendar/multimonth 
npm install lunar-javascript 
npm install element-plus

3.2 组件结构设计

  • 年份选择器:用于快速切换日历年份
  • 日历主体:使用 FullCalendar 实现的核心日历展示
  • 样式定制:美化日历显示效果

3.3 核心功能实现

<script setup>
import { ref,onMounted,watch } from 'vue'
import { Calendar } from '@fullcalendar/core'
import multiMonthPlugin from '@fullcalendar/multimonth'
import { Solar } from "lunar-javascript";

const selectYear = ref('')
const options = [
  {
    value: '2025',
    label: '2025',
  },
  {
    value: '2024',
    label: '2024',
  },
  {
    value: '2023',
    label: '2023',
  },
  {
    value: '2022',
    label: '2022',
  },
  {
    value: '2021',
    label: '2021',
  },
  {
    value: '2020',
    label: '2020',
  },
]

const calendar = ref(null)

// 公历节日:只保留中国法定的
const chinaSolarHolidays = {
  "01-01": "元旦",
  "05-01": "劳动节",
  "10-01": "国庆节",
};

const calendarOptions = ref({
  plugins: [multiMonthPlugin],
  initialView: 'multiMonthYear',
  locale: 'zh-cn', // 中文
  headerToolbar: {
    left: '',
    center: 'title',
    right: 'prev,next today',
  },
  buttonText: {
    today: '今天',
    day: '日',
    week: '周',
    month: '月'
  },
  slotMinTime: '00:00:00',
  slotMaxTime: '23:59:00',
  customButtons: {
    prev: {
      text: '前一年',
      click: () => switchYear(-1)
    },
    next: {
      text: '后一年',
      click: () => switchYear(1)
    },
    today: {
      text: '今年',
      click: () => {
        const thisYear = new Date().getFullYear()
        selectYear.value = thisYear.toString()
        calendar.value.gotoDate(new Date(thisYear, 0, 1))
      }
    }
  },
  dayCellContent: (arg) => {
    const date = arg.date;
    const dayNumber = date.getDate();

    // 阳历 & 农历
    const solar = Solar.fromDate(date);
    const lunar = solar.getLunar();

    // 1. 农历日期(初一、十五…)
    const lunarDay = lunar.getDayInChinese();
    const lunarMonth = lunar.getMonthInChinese(); // 正月、二月…

    // 2. 节气
    const term = lunar.getJieQi(); // 例如 "清明"

    // 3. 农历节日(春节、端午、中秋…)
    const lunarFestivals = lunar.getFestivals();

    // 4. 公历节日(只挑中国法定)
    const solarKey =
        String(solar.getMonth()).padStart(2, "0") +
        "-" +
        String(solar.getDay()).padStart(2, "0");
    const solarHoliday = chinaSolarHolidays[solarKey] || "";

    // 5. 优先级:节日 > 节气 > 农历月份(初一) > 农历日
    let displayText = lunarDay;
    if (lunarFestivals.length > 0) {
      displayText = lunarFestivals[0];
    } else if (solarHoliday) {
      displayText = solarHoliday;
    } else if (term) {
      displayText = term;
    } else if (lunarDay === "初一") {
      displayText = lunarMonth + '月'; // 正月、二月…
    }

    const isHoliday = lunarFestivals.length > 0 || !!solarHoliday;

    return {
      html: `
        <div class="fc-daygrid-day-number">${dayNumber}</div>
        <div class="fc-daygrid-lunar ${isHoliday ? "holiday" : ""}">
          ${displayText}
        </div>
      `,
    };
  },
})

// 下拉选择年份监听
watch(selectYear, (newYear) => {
  if (calendar.value) {
    calendar.value.gotoDate(new Date(parseInt(newYear), 0, 1))
  }
})

onMounted(() => {
  initCalendar()
})

// 初始化日历
function initCalendar() {
  let calendarEl = document.getElementById('calendar');
  calendar.value = new Calendar(calendarEl, calendarOptions.value)
  calendar.value.render()
}

// 添加年份切换函数
function switchYear(diff) {
  const currentYear = parseInt(selectYear.value || new Date().getFullYear())
  const newYear = currentYear + diff
  selectYear.value = newYear.toString()
  // 跳转日历到新年份
  if (calendar.value) {
    calendar.value.gotoDate(new Date(newYear, 0, 1)) // 1 月 1 日
  }
}
</script>

<template>
  <div class="select-year">
    <el-select v-model="selectYear" placeholder="Select" style="width: 240px" clearable>
      <el-option
          v-for="item in options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
      />
    </el-select>
  </div>
  <div class="calendar-wrapper">
    <div id="calendar"></div>
  </div>
</template>

<style scoped lang="less">
.calendar-wrapper /deep/ .fc .fc-daygrid-day-top {
  justify-content: center;
}
.calendar-wrapper /deep/ .fc .fc-daygrid-day-number {
  text-align: center;
}
.calendar-wrapper /deep/.fc-daygrid-lunar {
  font-size: 12px;
  color: #666;
}
.calendar-wrapper /deep/.fc-daygrid-lunar.holiday {
  color: red;
  font-weight: bold;
}
</style>
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐