Skip to content
on this page

扫码联系

编程学习&& IT

tian

前端万年历

在日常的开发中,无论是在前台客户端还是后台管理系统中,日历控件都是最为常见的。对于各位开发者来说,一般均会使用目前市场主流的UI组件库,但是如果目前让你实现一个可进行自定义DIV标注的日历,应当如何实现?

以下是常用的推荐UI库

常用UI组件库

Antd for React---日历

Antd for vue---日历

Vant 4---日历

本篇将以两种方式实现,h5版本 及其 vue3版本,最终实现的效果图如下所示。 屏幕截图 2023-12-17 123904.png

普通h5+js版本实现

js
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>制作年历</title>
    <style>
      body{text-align:center;}
      .box{margin:0 auto;width:880px;}
      .title{background: #ccc;}
      table{height:200px;width:200px;font-size:12px;text-align:center;float:left;margin:10px;font-family:arial;}
    </style>
    <script>
function calendar(y) {
  // 获取指定年份1月1日的星期数值,调整为星期一
  var w = (new Date(y, 0).getDay() + 6) % 7;
  var html = '<div class="box">';

  // 拼接每个月份的表格
  for (m = 1; m <= 12; m++) {
    html += '<table>';
    html += '<tr class="title"><th colspan="7">' + y + '' + m + ' 月</th></tr>';
    html += '<tr><td>一</td><td>二</td><td>三</td><td>四</td><td>五</td><td>六</td><td>日</td></tr>'

    // 获取每个月份共有多少天
    var max = new Date(y, m, 0).getDate();

    html += '<tr>';//开始<tr>标签
    for (d = 1; d <= max; d++) {
      if (w && d == 1) {//如果该月的第1天不是星期一,则填充空白
        html += '<td colspan ="' + w + '"> </td>';
      }
      html += '<td>' + d + '</td>';
      if (w == 6 && d != max) {//如果星期天不是该月的最后一天,则换行
        html += '</tr><tr>';
      } else if (d == max) {//该月的最后一天,闭合</tr>标签
        html += '</tr>';
      }
      w = (w + 1 > 6) ? 0 : w + 1;
    }
    html += '</table>';
  }
  html += '</div>';
  return html;
}

var year = parseInt(prompt('输入年份:','2023'));//制作弹窗
      document.write(calendar(year));//调用函数生成指定年份的年历
    </script>
  </head>
  <body>
  </body>
</html>

这里是使用vue3写法实现的效果 屏幕截图 2023-12-17 144151.png

js
<template>
  <div class="bg-white" style="width: 85vw; padding: 0px;">
    <div class="pl-20 pr-20 pt-10 flex justify-between" style="padding:40px 80px auto 80px; display: flex; justify-content: space-between; ">
      <div class="font-bold" style="font-weight: bold;">{{ state.YearNum }}</div>
      <div class="flex" style="display: flex;">
        <div class="flex items-center" style="display: flex;align-items: center;">
          <div class="bg-[#EDD5D5] p-1 font-bold" style="background-color: #edd5d5;padding: 4px; font-weight: 700;">00</div>
          <div>:标注1</div>
        </div>
        <div class="flex mx-5 items-center"  style="display: flex;align-items: center;">
          <div class="bg-[#D5E0ED] p-1 font-bold" style="background-color: #D5E0ED;padding: 4px; font-weight: 700;">00</div>
          <div>:标注2</div>
        </div>
        <div class="flex items-center" style="display: flex;align-items: center;">
          <div class="bg-[#666666] text-white font-bold p-1" style="color:white;background-color: #666;padding: 4px; font-weight: 700;">00</div>
          <div>:标注3</div>
        </div>
      </div>
    </div>
    <div id="calendar" v-if="state.YearNum !== 0" v-html="calendarHTML" @mouseover="showEvent" @mouseout="hideEvent">
    </div>
    <div id="eventDescription" :style="{ display: eventDescriptionVisible ? 'none' : 'none' }">
      {{ eventDescription }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, defineProps } from 'vue';

const props = defineProps({
  selectNums: {
    type: Number,
    required: true,
    default: 0,
  },
  data: {
    type: Array,
    required: false,
    default: () => [],
  },
});

const state = reactive({
  YearNum: 2023,
  dayList: [
  {
      date: '2023/01/01',
      rest_day: '1',
      sale_shut_day: '0',
      logistics_shut_day: '0',
      all_day:'0', 
      all_end_day:'0',
    },
    {
      date: '2023/01/02',
      rest_day: '0',
      sale_shut_day: '1',
      logistics_shut_day: '0',
      all_day:'0', 
      all_end_day:'0',
    },
    {
      date: '2023/01/03',
      rest_day: '0',
      sale_shut_day: '0',
      logistics_shut_day: '1',
      all_day:'0', 
      all_end_day:'0',
    },
    {
      date: '2023/01/04',
      rest_day: '0',
      sale_shut_day: '0',
      logistics_shut_day: '0',
      all_day:'1', 
      all_end_day:'0',
    },
    {
      date: '2023/01/05',
      rest_day: '0',
      sale_shut_day: '0',
      logistics_shut_day: '0',
      all_day:'0', 
      all_end_day:'1',
    },
  ],
});

// logistics_shut_day  物流  EDD5D5   sale_shut_day営業非稼働日  D5E0ED   两者 #666

const calendarHTML = ref('');

function generateCalendar() {
  calendarHTML.value = calendar(state.YearNum);
}

function calendar(y: number) {
  let w = (new Date(y, 0).getDay() + 6) % 7;
  let html = '<div class="boxday">';
  const specialEvents: Record<string, { sale_shut_day: string; logistics_shut_day: string ,rest_day:string,all_day:string,all_end_day:string}> = {};
  for (const item of state.dayList) {
    const dateParts = item.date.match(/\d{4}\/\d{2}\/\d{2}/);
    if (dateParts) {
      const date = dateParts[0].replace(/\//g, '-');
      specialEvents[date] = {
        sale_shut_day: item.sale_shut_day,
        logistics_shut_day: item.logistics_shut_day,
        rest_day:item.rest_day,
        all_day:item.all_day,
        all_end_day:item.all_end_day
      };
    }
  }

  function isSpecialDate(month: number, day: number) {
    const date = `${y}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
    return specialEvents[date] !== undefined;
  }

  function getSpecialEvent(month: number, day: number) {
    const date = `${y}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
    return specialEvents[date] || { sale_shut_day: '', logistics_shut_day: '',rest_day:'',all_day:'' };
  }

  for (let m = 1; m <= 12; m++) {
    html += '<table>';
    html += '<tr class="title"><th colspan="7">' + m + '月</th></tr>';
    html +=
      '<tr class="topdesc"><td>月</td><td>火</td><td>水</td><td>木</td><td>金</td><td style="color:#A62211">土</td><td  style="color:#A62211">日</td></tr>';
    const max = new Date(y, m, 0).getDate();
    html += '<tr>';
    for (let d = 1; d <= max; ++d) {
      if (w && d == 1) {
        html += '<td colspan="' + w + '"></td>';
      }
      const specialClass = isSpecialDate(m, d) ? 'special-date' : '';
      const isSaturday = w === 5;
      const isSunday = w === 6;
      const dayClass = isSaturday ? 'day red-sunday' : isSunday ? 'day red-sunday' : 'day';
      const event = getSpecialEvent(m, d);
      const isLogisticsShutDay = event.logistics_shut_day === '1'; // 情况1
      const issale_shut_day = event.sale_shut_day === '1'; // 情况2
      const isrest_day = event.rest_day === '1'; //情况3
      const isall_day = event.all_day ==='1'; // 情况4
      const isall_end_day = event.all_end_day ==='1';
      const logisticsShutClass = isLogisticsShutDay ? 'logistics-shut-day' : '';
      const saleShutClass = issale_shut_day ? 'shut-day' : '';
      const restClass =isrest_day ? 'rest-day' :'';
      const allDayClass = isall_day  ?'all-shut-log-day' :'';
      const allEndDayClass = isall_end_day ? 'all-end-day' :''; 
      
      
      html +=
        '<td class="' + dayClass + ' ' + specialClass + ' ' + logisticsShutClass + '' + saleShutClass + ''+restClass+'' + allDayClass + ' '+allEndDayClass+'" data-event="' + event + '">' + d + '</td>';
      if (w == 6 && d != max) {
        html += '</tr><tr>';
      } else if (d == max) {
        html += '</tr>';
      }
      w = w + 1 > 6 ? 0 : w + 1;
    }
    html += '</table>';
  }

  html += '</div>';
  return html;
}

const eventDescription = ref('');
const eventDescriptionVisible = ref(false);

const showEvent = (event: MouseEvent) => {
  if ((event.target as HTMLElement).classList.contains('special-date')) {
    eventDescription.value = (event.target as HTMLElement).getAttribute('data-event') || '';
    eventDescriptionVisible.value = true;
  }
};

const hideEvent = () => {
  eventDescriptionVisible.value = false;
};

generateCalendar();
</script>

<style scoped>
:deep(.boxday .logistics-shut-day) {
  color: #333 !important;
  background-color: #EDD5D5 !important;
}

:deep(.boxday .shut-day) {
  background-color: #D5E0ED !important;
  color: #333 !important;
}

:deep( .boxday .rest-day) {
  border-radius: 50% !important;
  border: 1px solid red;
}


:deep(.boxday .all-shut-log-day) {
  background-color: #666 !important;
  color: #fff !important;
}

:deep(.boxday .all-end-day){
  background-color: #666 !important;
  color: #fff !important;
  border: 1px solid red !important;
  border-radius: 50% !important;
}
:deep(.boxday) {
  display: flex;
  flex-wrap: wrap;
}

:deep(.special-date:active) {
  position: relative;
  background-color: #135ebf !important;
  color: #fff !important;
}

:deep(.special-date:hover) {
  position: relative;
  background-color: #135ebf !important;
  color: #fff !important;
}

:deep(.special-date) {
  position: relative;
  /* background-color: #ffe8e5 !important; */
  color: #333;
}

:deep(table) {
  height: 340px;
  min-width: 20%;
  max-width: 20%;
  font-size: 16px;
  text-align: center;
  float: left;
  margin: 35px;
  font-family: '优设标题黑';
  transform: scale(0.8);
}

:deep(.title) {
  width: 50px;
  background: #f6f6f6;
  color: #333;
  text-align: left;
  margin-bottom: -5px;
}

:deep(.topdesc) {
  font-weight: 900;
  border-bottom: 2px solid #ccc;
}

:deep(.day.red) {
  color: #135ebf;
}

:deep(.day.red-sunday) {
  color: #a62211;
}
</style>