Criação de um Componente de Calendário Personalizado com Vue.js e Element UI

Este guia demonstra como construir um componente de calendário interativo utilizando Vue.js e a biblioteca de compnoentes Element UI. Abordaremos a configuração, a integração de funcionalidades de calendário lunar e a exibição de feriados.

Instalação de Dependências

Para começar, instale as bibliotecas necessárias para manipulação de datas e calendário lunar.


npm install moment@2.29.4 --save
npm install lunar-javascript@1.6.7 --save
 

Definição de Feriados Mundiais

Crie um arquivo, por exemplo, utils/holidays.js, para armazenar uma lista de feriados internacionais. Esta lista servirá como base para a exibição de datas comemorativas.


export const worldHolidays = [
 { month: 1, day: 1, name: 'Ano Novo' },
 { month: 2, day: 14, name: 'Dia dos Namorados' },
 { month: 3, day: 8, name: 'Dia Internacional da Mulher' },
 { month: 4, day: 1, name: 'Dia da Mentira' },
 { month: 4, day: 22, name: 'Dia da Terra' },
 { month: 5, day: 1, name: 'Dia do Trabalhador' },
 { month: 12, day: 25, name: 'Natal' },
];
 

Utilitários de Formatação de Data

Adicione funções auxiliares ao seu arquivo de utilitários (ex: utils/dateUtils.js) para facilitar a formatação de datas e a conversão de timestamps.


// Adiciona um método de formatação ao protótipo Date
Date.prototype.formatDate = function (formatString) {
 const dateParts = {
   'M+': this.getMonth() + 1,
   'd+': this.getDate(),
   'h+': this.getHours(),
   'm+': this.getMinutes(),
   's+': this.getSeconds(),
   'q+': Math.floor((this.getMonth() + 3) / 3),
   'S': this.getMilliseconds(),
 };

 if (/(y+)/.test(formatString)) {
   formatString = formatString.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
 }

 for (let key in dateParts) {
   if (new RegExp('(' + key + ')').test(formatString)) {
     const replacement = RegExp.$1.length === 1
       ? dateParts[key]
       : ('00' + dateParts[key]).substr(('' + dateParts[key]).length);
     formatString = formatString.replace(RegExp.$1, replacement);
   }
 }
 return formatString;
};

// Função para converter timestamp em objeto Date
Date.transferTimestamp = function (timestamp) {
 if (!timestamp) {
   // Retornar um valor padrão ou lançar um erro se o timestamp for inválido
   return null;
 }
 return new Date(timestamp);
};
 

Componente de Calendário

Implemente o componente Vue, utilizando o el-calendar do Element UI e integrando as funções de calendário lunar e feriaods.


<template>
 <div class="calendar-container">
   <section>
     <div class="navigation-buttons">
       <el-button @click="previousYear" size="mini">Ano Anterior</el-button>
       <el-button @click="nextYear" size="mini">Próximo Ano</el-button>
     </div>

     <el-calendar v-model="currentDate" ref="calendar">
       <template #dateCell="{ date, data }">
         <div class="cell-content">
           <div class="month-day">
             {{ Date.transferTimestamp(data.day).formatDate("MM-dd") }}
           </div>
           <div class="lunar-date">{{ formatLunarDate(date) }}</div>
           <div class="solar-term" v-html="getSolarTerm(getLunarYearMonthDay('y', date), getLunarYearMonthDay('m', date), getLunarYearMonthDay('d', date))"></div>
           <div class="traditional-holiday">{{ getTraditionalHoliday(parseInt(data.day.split("-")[1]), parseInt(data.day.split("-")[2])) }}</div>
         </div>
         <div>{{ getOtherFestivals(getLunarYearMonthDay("y", date), getLunarYearMonthDay("m", date), getLunarYearMonthDay("d", date)) }}</div>
       </template>
     </el-calendar>
   </section>
 </div>
</template>

<script>
import moment from "moment"
import lunar from "lunar-javascript"
import { worldHolidays } from "@/utils/holidays" // Ajuste o caminho conforme necessário

export default {
 name: "CustomCalendar",
 data() {
   return {
     currentDate: null,
     displayDate: moment(new Date()),
   }
 },
 methods: {
   previousYear() {
     this.navigateYear("-");
   },
   nextYear() {
     this.navigateYear("+");
   },
   navigateYear(direction) {
     let targetYear = this.displayDate.year() + (direction === "+" ? 1 : -1);
     let newDate = moment(`${targetYear}-${moment().month() + 1}-${moment().date()}`);
     this.currentDate = newDate.toDate();
     this.displayDate = newDate;
   },
   convertMomentToDate(momentObj) {
     return momentObj.toDate();
   },
   synchronizeDisplayDate(dateObj) {
     this.displayDate = moment(dateObj);
   },
   formatLunarDate(solarDate) {
     // Retorna apenas a parte do mês e dia do calendário lunar
     return lunar.Solar.fromDate(solarDate).getLunar().toString().split("年")[1];
   },
   getTraditionalFestival(month, day) {
     const festival = worldHolidays.find(f => f.month === month && f.day === day);
     return festival ? festival.name : "";
   },
   getSolarTerm(year, month, day) {
     const lunarDate = lunar.Lunar.fromYmd(year, month, day);
     const solarTerm = lunarDate.getJieQi();
     if (solarTerm) {
       const termTime = lunarDate.getJieQiTable()[solarTerm].toYmdHms().match(/\d\d:\d\d:\d\d/)[0];
       return `<span style="font-family: 'zkxw'">${solarTerm}</span> ${termTime}`;
     }
     return "";
   },
   getLunarYearMonthDay(type, date) {
     const lunarDate = lunar.Lunar.fromDate(date);
     switch (type) {
       case "y": return lunarDate.getYear();
       case "m": return lunarDate.getMonth();
       case "d": return lunarDate.getDay();
       default: return null;
     }
   },
    getOtherFestivals(year, month, day) {
     const lunarDate = lunar.Lunar.fromYmd(year, month, day);
     const festivals = lunarDate.getFestivals();
     return festivals.length > 0 ? festivals[0] : "";
   },
    getTraditionalHolidaysFromLunar(year, month, day) {
      const lunarDate = lunar.Lunar.fromYmd(year, month, day);
      const holiday = lunarDate.getOtherFestivals();
      return holiday.length > 0 ? holiday[0] : "";
    }
 },
 mounted() {
   this.currentDate = this.convertMomentToDate(this.displayDate);
 },
}
</script>

<style lang="less" scoped>
.calendar-container {
 width: 100%;
 height: 100%;
 font-family: "lgq";
 position: relative;
 font-size: 0.8rem; // Ajuste o tamanho base conforme necessário

 .navigation-buttons {
   position: absolute;
   top: 1rem;
   right: 5rem;
   z-index: 10;

   .el-button + .el-button {
     margin-left: 0;
   }
 }

 ::v-deep .el-button {
   border: none;
   &:hover {
     color: #409eff;
     background-color: #dae6f5;
   }
 }

 ::v-deep .el-calendar-table .el-calendar-day {
   display: flex;
   align-items: center;
   justify-content: center;
   min-height: 80px; // Ajustar altura mínima para acomodar conteúdo
 }

 ::v-deep .el-calendar__body {
   padding: 0;
 }

 ::v-deep .el-calendar-table td.is-today {
   color: #409eff;
 }

 .cell-content {
   text-align: center;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   width: 100%;

   .month-day {
     font-size: 0.7rem;
     font-weight: bold;
     margin-bottom: 0.2rem;
     border-radius: 0.2rem;
     padding: 0.1rem 0.3rem;
   }

   .lunar-date {
     font-size: 0.6rem;
     margin-bottom: 0.1rem;
   }

   .solar-term {
     color: blue;
     font-size: 0.5rem;
   }

   .traditional-holiday {
     color: red;
     font-family: "dyh"; // Certifique-se que esta fonte está carregada
     font-size: 0.7rem;
   }
 }
}

/* Adaptação para telas menores (Mobile) */
@media screen and (max-width: 768px) {
 .calendar-container {
   font-size: 0.7rem;

   .navigation-buttons {
     top: 0.5rem;
     right: 1rem;
   }

   ::v-deep .el-calendar-table .el-calendar-day {
      min-height: 60px;
   }

   .cell-content {
     .month-day {
       font-size: 0.6rem;
     }
     .lunar-date {
       font-size: 0.5rem;
     }
     .solar-term {
       font-size: 0.4rem;
     }
     .traditional-holiday {
        font-size: 0.6rem;
     }
   }
 }
}
</style>
 

Tags: Vue.js Element UI Componente de Calendário javascript npm

Publicado em 7-26 22:30