前端万年历
在日常的开发中,无论是在前台客户端还是后台管理系统中,日历控件都是最为常见的。对于各位开发者来说,一般均会使用目前市场主流的UI组件库,但是如果目前让你实现一个可进行自定义DIV标注的日历,应当如何实现?
以下是常用的推荐UI库
常用UI组件库
本篇将以两种方式实现,h5版本 及其 vue3版本,最终实现的效果图如下所示。
普通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写法实现的效果
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>