日历使用背景
内网环境,无法使用外网及时同步节假日信息,所以要在内网创建日历,每年年初或头年年末同步一次节假日信息, 后续变更自己手动维护。
发现element-ui的日历组件并不能满足我们的要求, 组件的属性太少,于是我仿着手搓了一个日历出来, 本着授之于鱼不如授之以渔的原则, 我将把思路和整体流程跟大家讲一下, 其他因为不同需求的关系, 需要大家自己丰衣足食。
-
话不多说,先看效果图
日历效果图
-
日历创建规则
首先我们要确定日历头部, 是一二三四五六日, 还是日一二三四五六,这影响到我们生成日期的规则,我们暂时将前者定为规则一,后者定为规则二。
第一步,我们要确定当月1号是周几, 拿今年9月为例, 2023年9月1号是周五, 按照规则一则需要展示上月4天的日期, 而按照规则二则需要向前5天。而后面的日期就很好办了, 按照正常日历,应该展示6*7方格, 42天的数据, 我们只需要找到当前展示的第一天,往后+41天就可以了。
代码
-
前台vue
前台代码没什么逻辑, 我把日期操作都放到了后台, 因为要同步节假日和其他操作, 所以放到后台是最好的选择, 前台代码直接复制粘贴就能用。
<template>
<div class="app-main">
<div class="demo-block">
<div class="source">
<div class="el-calendar">
<div class="el-calendar__header">
<div class="el-calendar__title">{{queryParams.year}}年{{queryParams.month}}月</div>
<div class="el-calendar__button-group">
<div class="el-button-group">
<el-button type="button" size="mini" @click="lastMonth">上个月</el-button>
<el-button type="button" size="mini" @click="toDay">今天</el-button>
<el-button type="button" size="mini" @click="nextMonth">下个月</el-button>
</div>
</div>
</div>
<div class="el-calendar__body">
<table class="el-calendar-table" cellspacing="0" cellpadding="0" >
<thead>
<th style="color: red;font-size: 20px;font-weight: 600;">日</th>
<th style="font-size: 20px;font-weight: 600;">一</th>
<th style="font-size: 20px;font-weight: 600;">二</th>
<th style="font-size: 20px;font-weight: 600;">三</th>
<th style="font-size: 20px;font-weight: 600;">四</th>
<th style="font-size: 20px;font-weight: 600;">五</th>
<th style="color: red;font-size: 20px;font-weight: 600;">六</th>
</thead>
<tbody>
<tr
v-for="item in (holidayList)"
:key="item.id"
>
<td
class="current"
v-for="(col, colIdx) in item"
:key="colIdx"
>
<div @click="selectDay(col)" :class="col.curDate==selectId?'el-calendar-day-active':'el-calendar-day'" :style="{color: col.isGray==0?(col.type==1||col.type==2?'red':'black'):(col.type==1||col.type==2?'#F3BCBC':'gray')}">
<span>{{col.day}}</span><br>
<span>{{col.showName}}</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
export default {
name: "holiday",
data() {
return {
queryParams: {
id: null,
type: null,
year: new Date().getFullYear(),
month: new Date().getMonth()+1,
},
generateYear: new Date().getFullYear(),
showSearch: true,
holidayList: [],
selectId: this.initSearchDate()
};
},
mounted() {
},
created() {
this.initDate();
},
methods: {
initSearchDate() {
var nowDate = new Date();
var date = {
year: nowDate.getFullYear(),
month: nowDate.getMonth() + 1,
day: nowDate.getDate()
}
return date.year + '-' + (date.month >= 10 ? date.month : '0' + date.month) + '-' + (date.day >= 10 ? date.day : '0' + date.day);
},
initDate(){
this.holidayList= [];
listHolid(this.queryParams).then(response => {
let list = response.data;
for (let row = 0; row < 6; row++) {
this.holidayList.push(list.splice(0, 7));
}
});
},
selectDay(col){
this.selectId = col.curDate;
this.queryParams.id = col.id;
},
lastMonth(){
if(this.queryParams.month==1){
this.queryParams.year --;
this.queryParams.month = 12;
}else{
this.queryParams.month --;
}
this.initDate();
},
toDay(){
this.queryParams.year= new Date().getFullYear();
this.queryParams.month = new Date().getMonth()+1;
this.selectId = this.initSearchDate();
this.initDate();
},
nextMonth(){
if(this.queryParams.month==12){
this.queryParams.year ++;
this.queryParams.month = 1;
}else{
this.queryParams.month ++;
}
this.initDate();
}
}
};
</script>
<style lang="scss" scoped>
.app-main{
background-color: #f6f6f6;
}
.demo-block {
background-color: #fff;
margin-top: 80px;
margin-left: 25%;
border: 1px solid #ebebeb;
border-radius: 25px;
width: 870px;
height: 710px;
.source{
padding: 24px;
}
}
.el-calendar {
background-color: #fff;
}
.el-calendar__header {
display: flex;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid #ebeef5;
}
.el-calendar__title {
color: #000;
align-self: center;
}
.el-button-group {
display: inline-block;
vertical-align: middle;
}
.el-calendar__body {
padding: 12px 20px 35px;
}
table {
display: table;
border-collapse: separate;
box-sizing: border-box;
text-indent: initial;
border-spacing: 1px;
border-color: gray;
}
.el-calendar-table {
table-layout: fixed;
width: 100%;
tr {
display: table-row;
vertical-align: inherit;
border-color: inherit;
}
}
.current{
border: 1px solid #ebeef5;
vertical-align: top;
transition: background-color .2s ease;
}
.el-calendar-day{
box-sizing: border-box;
padding: 6px;
height: 85px;
line-height: 35px;
text-align: center;
:hover{
cursor: pointer;
background-color: #f2f8fe;
}
}
.el-calendar-day-active{
box-sizing: border-box;
padding: 6px;
height: 85px;
line-height: 35px;
text-align: center;
background-color: #c9dff4;
}
.el-calendar-day-gray{
box-sizing: border-box;
padding: 6px;
height: 85px;
line-height: 35px;
text-align: center;
:hover{
cursor: pointer;
background-color: #f2f8fe;
}
}
tbody {
display: table-row-group;
vertical-align: middle;
border-color: inherit;
}
</style>
-
后台代码
先看一下表结构, 表名称holiday_info, 表结构大家可根据自己需求自行构建
-
实体类
isGray用来区分当前日期是否属于本月, type用来判断当前日期是否为假期和周末(即前台标红)
/**
*
* @author bobo
* @date 2023-09-23
*/
@Data
public class HolidayInfo extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long id;
/** 年 */
private Integer year;
/** 月 */
private Integer month;
/** 日 */
private Integer day;
/** 节日名称 */
private String name;
/** 展示内容 */
private String showName;
/** 星期 */
private String weekName;
/** 0工作日 1周末 2节日 3调休 */
private Integer type;
private String typeStr;
/** 用于同步API节假日的key */
private String keyDate;
/** 日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date curDate;
/** 开始日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/** 结束日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
/** 农历日 */
private String lunars;
/** 农历日期 */
private String lunarsDate;
/** 是否置灰 0不置灰 1置灰 */
private Integer isGray;
}
-
生成年度日历
生成农历日期的话, 网上搜索资源就可以了, 因为不是很重要而且网上资源很多我就不占地方了,如果找不到或者懒得找,参考如下链接:
https://blog.csdn.net/weixin_43860634/article/details/128847454
原创作者: 飞翔的佩奇
/**
* 生成年度日期
*
* @param holidayInfo 节假日实体类
* @return 结果
*/
@Override
public AjaxResult generateYearData(HolidayInfo param){
Integer year = param.getYear()==null?Integer.valueOf(DateUtils.dateTimeNow("yyyy")):param.getYear();
holidayInfoMapper.deleteHolidayInfoByYear(year);
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(GregorianCalendar.DAY_OF_YEAR, 1);
calendar.set(GregorianCalendar.YEAR, year);
// 循环输出一年的日期
List<HolidayInfo> list = new ArrayList<>();
while (calendar.get(GregorianCalendar.YEAR) == year) {
Date date = calendar.getTime();
HolidayInfo holidayInfo = new HolidayInfo();
holidayInfo.setYear(calendar.get(GregorianCalendar.YEAR));
holidayInfo.setMonth(calendar.get(GregorianCalendar.MONTH)+1);
holidayInfo.setDay(calendar.get(GregorianCalendar.DATE));
//周名称
holidayInfo.setWeekName(new DateFormatSymbols().getWeekdays()[calendar.get(GregorianCalendar.DAY_OF_WEEK)]);
int weekDay = calendar.get(GregorianCalendar.DAY_OF_WEEK);
int boo = (weekDay==1||weekDay==7)?1:0;
//判断日期类型 0工作日 1周末 2节假日 3调休
holidayInfo.setType(boo);
holidayInfo.setKeyDate(DateFormatUtils.format(date, "MM-dd"));
holidayInfo.setCurDate(date);
CalendarUtils as = CalendarUtils.as(date);
//农历日 初一~三十
holidayInfo.setLunars(as.getChinaDayString());
//农历日 例如: 二零三年九月初一
holidayInfo.setLunarsDate(as.getChinaString());
list.add(holidayInfo);
calendar.add(GregorianCalendar.DAY_OF_YEAR, 1);
}
holidayInfoMapper.insertBatch(list);
return AjaxResult.success(year+"年度日历已生成");
}
-
日历查询
/**
* 日历查询
*
* @param holidayInfo
* @return AjaxResult
*/
@Override
public AjaxResult selectHolidayInfoList(HolidayInfo holidayInfo )
{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
//处理日期
Integer year = holidayInfo .getYear();
Integer month = holidayInfo .getMonth();
GregorianCalendar calendar = new GregorianCalendar();
// 重置时间为当月月初 例: 2023-09-01
calendar.set(GregorianCalendar.YEAR, year);
calendar.set(GregorianCalendar.MONTH, month-1);
calendar.set(GregorianCalendar.DAY_OF_MONTH, 1);
try {
// 获取当月月初的week 需要注意的是 1代表着周末
int week = calendar.get(GregorianCalendar.DAY_OF_WEEK);
week = week==1?7:week-1;
// 本月日历展示的起始日期 例: 2023-08-27
calendar.add(GregorianCalendar.DAY_OF_YEAR, -week);
holidayInfo .setStartDate(df.parse(df.format(calendar.getTime())));
// 本月日历展示的结束日期 例: 2023-10-07
calendar.add(GregorianCalendar.DAY_OF_YEAR, 41);
holidayInfo .setEndDate(df.parse(df.format(calendar.getTime())));
List<HolidayInfo> list = holidayInfoMapper.selectHolidayInfoList(holidayInfo );
list.stream().forEach(item->{
item.setIsGray(item.getMonth()==month?0:1);
});
return AjaxResult.success(list);
} catch (ParseException e) {
e.printStackTrace();
return AjaxResult.error("时间转换异常");
}
}
<sql id="selectHolidayInfoVo">
select
id,
year,
month,
day,
name,
if((name is null or name = ''), lunars, name) showName,
week_name,
type,
case type
when 0 then '工作日'
when 1 then '周末'
when 2 then '节假日'
when 3 then '调休'
else '' end typeStr,
key_date,
cur_date,
lunars
from holiday_info
</sql>
<select id="selectHolidayInfoList" parameterType="HolidayInfo" resultMap="HolidayInfoResult">
<include refid="selectHolidayInfoVo"/>
<where>
<if test="startDate != null "> and cur_date >= #{startDate}</if>
<if test="endDate != null "> and cur_date <= #{endDate}</if>
</where>
</select>
-
同步节假日
我用api是提莫的神秘小站, 链接:免费节假日 API - 提莫的神秘小站
代码贴上:
/**
* 请求获取节假日信息
*/
public static String getHoliday(Integer year) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try{
URL realUrl = new URL("https://timor.tech/api/holiday/year/" + year);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null)
{
result.append(line);
}
}catch (Exception e) {
System.out.println("调用HttpUtils.sendGet ConnectException: " + e);
}
finally {
try {
if (in != null) {
in.close();
}
}
catch (Exception ex) {
System.out.println("调用in.close Exception: "+ ex);
}
}
return result.toString();
}
对于返回来的JSON数据,大家随意处理就行, 能看到我们只是对name和type进行了处理, 当然我选择了不用动脑,最笨的双重for循环
/**
* 同步节假日
*
* @param holidayInfo
* @return 结果
*/
@Override
public AjaxResult syncHoliday(HolidayInfo holidayInfo){
Integer year = holidayInfo.getYear()==null?Integer.valueOf(DateUtils.dateTimeNow("yyyy")):holidayInfo.getYear();
String result = HldjcUtils.getHoliday(year);
JSONObject parse = (JSONObject)JSONObject.parse(result);
JSONObject holidayJson = (JSONObject)parse.get("holiday");
Map<String, Object> map = holidayJson.toJavaObject(Map.class);
List<HolidayInfo > holidayList = holidayInfoMapper.selectHolidayInfoListByYear(year);
for (HolidayInfo holiday: holidayList) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if(holiday.getKeyDate().equals(key)){
JSONObject valueJson = (JSONObject)JSONObject.parse(String.valueOf(entry.getValue()));
Boolean holidayFlag = (Boolean)valueJson.get("holiday");
String name = (String)valueJson.get("name");
holiday.setType(holidayFlag?2:3);
holiday.setName(name);
holidayInfoMapper.syncHolidayInfo(holiday);
}
}
}
return AjaxResult.success();
}
到这一步就基本完事了,后续可以在日历上进行配置,或者生成操作。生成年度的时候,建议对已同步节假日的年度进行校验,否则就会覆盖了。而且对配置过的年度是否可以再次生成就要看实际的应用场景了。好了相信我讲的应该也够清楚了,大家一起努力吧