前后端实现假期日历自定义,包括调休上班以及休息的标注

最近有开发一个假期日历的需求,就类似于手机上的日历能显示某一天休息或者调休这种。大体上的框架引用了这篇文章,感谢前端设置工作日日历_前端日历-CSDN博客这篇文章的基础框架。

因为原始的页面所实现的功能不能很好的和业务契合  因此自己又做了修改。主要是增加了自定义某一天的类型以及一些其他的保存,删除的接口啥的,具体的效果可以看效果图。

废话少说  直接上源码,主体也是用了elementui。
 

<template>
  <div>
    <!-- 顶部选择栏 -->
    <div class="calendar-top">
      <select v-model="selectedYear" @change="changeCalendar">
        <option v-for="year in years" :key="year" :value="year">{{ year }} 年</option>
      </select>
      <el-button type="info" size="mini" @click="remark" icon="el-icon-message" class="save-btn">
        功能解释说明
      </el-button>
      <el-button type="danger" size="mini" @click="deleteAllDate" class="save-btn">
        一键清空所有节假日
      </el-button>
      <el-button type="primary" size="mini" @click="saveSelectedDate" icon="el-icon-folder" class="save-btn">
        保存选中日期
      </el-button>

    </div>

    <!-- 日历容器 -->
    <div class="calendar-container">
      <div v-for="(monthData, index) in monthData" :key="index" class="calendar">
        <div class="calendar-header">{{ selectedYear }} 年 {{ months[monthData.month - 1] }}</div>
        <div class="calendar-grid">
          <div class="calendar-day-header" v-for="day in dayHeaders" :key="day">
            {{ day }}
          </div>
          <div
            class="calendar-day"
            v-for="(day, dayIndex) in monthData.days"
            :key="`${monthData.month}-${dayIndex}`"
            @click="openHolidaySetting(monthData.month, day)"
          >
            <span>{{ day.date }}</span>
            <span v-if="day.status" :class="['status-label', day.status === '班' ? 'status-label-work' : 'status-label-holiday']">
              {{ day.status }}
            </span>
          </div>
        </div>
      </div>
    </div>

    <!-- 假期设置弹出框 -->
    <el-dialog :visible.sync="holidaySettingVisible" title="假期设置">
      <el-form :model="holidaySettingForm" label-width="80px">
        <el-form-item label="假期类型">
          <el-select v-model="holidaySettingForm.holidayType" placeholder="请选择假期类型">
            <!-- 固定选项“无”,value为空 -->
            <el-option label="无" value=""></el-option>
            <el-option
              v-for="holidayType in holiday_typeList"
              :key="holidayType.id"
              :label="holidayType.name"
              :value="holidayType.name"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="holidaySettingVisible = false">取消</el-button>
          <el-button type="primary" @click="saveHolidaySetting">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script>
import { addHoliday_calendar, deleteHoliday_calendar, holiday_calendar } from '@/api/attendance_manger/holiday_calendar/holiday_calendar.js';
import { listHoliday_type } from "@/api/attendance_manger/holiday_type/holiday_type";

export default {
  data() {
    return {
      selectedYear: new Date().getFullYear(),
      years: [],
      months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
      dayHeaders: ['一', '二', '三', '四', '五', '六', '日'],
      // 后端已有数据(JSON字符串,不直接修改)
      holidaySettingsJson: '{}',
      // 新增数据:仅存储前端新增的假期日期(后端不存在的记录)
      needAddHolidaySettingsJson: '{}',
      // 删除数据:存储需要删除的记录(后端已有数据)
      needRemoveHolidaySettingsJson: '{}',
      markedDates: [],
      monthData: [],
      holidaySettingVisible: false,
      holidaySettingForm: { holidayType: null },
      currentSelectedDay: null,           // 当前点击的日期对象
      currentSelectedMonthData: null,       // 当前点击的月份数据({ month: number })
      holiday_typeList: []                  // 假期类型数据
    };
  },
  async mounted() {
    await this.fetchHolidayCalendar();
    this.generateYearOptions();
    this.getList();
    this.generateCalendarData();
  },
  methods: {
    formatDate(year, month, day) {
      return `${year}-${month}-${day}`;
    },
    generateYearOptions() {
      for (let i = 2023; i <= 2060; i++) {
        this.years.push(i);
      }
    },
    changeCalendar() {
      this.fetchHolidayCalendar();
      this.generateCalendarData();
    },
    // 从后端获取假期数据
    async fetchHolidayCalendar() {
      const res = await holiday_calendar({ year: this.selectedYear });
      if (res.code === 200) {
        this.holidaySettingsJson = res.data.holidayCalendar || '{}';
      } else {
        this.holidaySettingsJson = '{}';
      }
      this.generateCalendarData();
    },
    // 获取假期类型列表
    getList() {
      listHoliday_type().then(response => {
        if (response.code === 200) {
          this.holiday_typeList = response.rows;
        }
      }).catch(error => console.error('获取假期类型失败:', error));
    },
    // 生成月数据以及每月的日期数据
    generateCalendarData() {
      // 解析后端、前端新增和前端删除数据
      let backendData = {};
      let addData = {};
      let removeData = {};
      try {
        backendData = JSON.parse(this.holidaySettingsJson || '{}');
      } catch (e) {
        console.error('解析后端数据失败:', e);
      }
      try {
        addData = JSON.parse(this.needAddHolidaySettingsJson || '{}');
      } catch (e) {
        console.error('解析新增数据失败:', e);
      }
      try {
        removeData = JSON.parse(this.needRemoveHolidaySettingsJson || '{}');
      } catch (e) {
        console.error('解析删除数据失败:', e);
      }
      // 合并后端数据和新增数据
      let mergedData = { ...backendData, ...addData };
      // 对于需要删除的日期,从合并数据中剔除
      Object.keys(removeData).forEach(date => {
        delete mergedData[date];
      });

      // 清空 monthData 及 markedDates 重新生成
      this.monthData = [];
      this.markedDates = [];

      for (let m = 1; m <= 12; m++) {
        const days = this.generateMonthDays(this.selectedYear, m, mergedData);
        this.monthData.push({ month: m, days });
      }
    },
    generateMonthDays(year, month, mergedData) {
      const firstDayOfMonth = new Date(year, month - 1, 1);
      let startingDay = firstDayOfMonth.getDay() - 1;
      if (startingDay === -1) {
        startingDay = 6;
      }
      const daysInMonth = new Date(year, month, 0).getDate();
      const days = [];
      // 填充前导空白
      for (let i = 0; i < startingDay; i++) {
        days.push({ date: '', isCurrentMonth: false, status: '' });
      }
      // 填充当月日期
      for (let i = 1; i <= daysInMonth; i++) {
        const dateStr = this.formatDate(year, month, i);
        let status = '';
        if (mergedData[dateStr]) {
          // 根据假期类型决定状态:如果包含“调休”显示“班”,否则显示“休”
          const keywords = ['调休', '补班', '加班'];
          status = keywords.some(key => mergedData[dateStr].includes(key)) ? '班' : '休';
          if (!this.markedDates.includes(dateStr)) {
            this.markedDates.push(dateStr);
          }
        }
        days.push({ date: i, isCurrentMonth: true, status });
      }
      return days;
    },
    // 打开假期设置弹窗,传入月份数据和对应日期对象
    openHolidaySetting(month, day) {
      if (!day.date) return; // 前导空白不处理
      this.currentSelectedDay = day;
      this.currentSelectedMonthData = { month };
      this.holidaySettingForm.holidayType = null;
      this.holidaySettingVisible = true;
    },
    // 保存假期设置
    saveHolidaySetting() {
      const { holidayType } = this.holidaySettingForm;
      if (holidayType !== null) {
        const dateString = this.formatDate(this.selectedYear, this.currentSelectedMonthData.month, this.currentSelectedDay.date);
        try {
          const backendData = JSON.parse(this.holidaySettingsJson || '{}');
          let addData = JSON.parse(this.needAddHolidaySettingsJson || '{}');
          let removeData = JSON.parse(this.needRemoveHolidaySettingsJson || '{}');

          if (holidayType === '') {
            // 用户选择“无”:取消假期
            // 如果该日期存在于后端数据中,则标记删除
            if (backendData[dateString]) {
              removeData[dateString] = true;
            }
            // 如果该日期存在于新增数据中,则直接删除
            if (addData[dateString]) {
              delete addData[dateString];
            }
            // 更新当前页面显示
            this.currentSelectedDay.status = '';
            const index = this.markedDates.indexOf(dateString);
            if (index > -1) {
              this.markedDates.splice(index, 1);
            }
          } else {
            // 用户选择了某个假期类型
            // 如果该日期在后端已有数据中,则不允许新增(你可以扩展为更新逻辑)
            if (backendData[dateString]) {
              // 如果之前标记了删除,则取消删除操作
              if (removeData[dateString]) {
                delete removeData[dateString];
              }
              // 提示用户该日期数据已存在,不做新增
              this.$message.warning('该日期数据已存在后端,无法新增,请先删除后再修改!');
            } else {
              // 新增数据写入 needAddHolidaySettingsJson
              addData[dateString] = holidayType;
              // 更新显示状态,假设“调休”显示“班”,其它显示“休”
              this.currentSelectedDay.status = holidayType.includes('调休') ? '班' : '休';
              if (!this.markedDates.includes(dateString)) {
                this.markedDates.push(dateString);
              }
            }
          }
          // 更新前端的新增和删除 JSON 字符串
          this.needAddHolidaySettingsJson = JSON.stringify(addData);
          this.needRemoveHolidaySettingsJson = JSON.stringify(removeData);
          this.generateCalendarData(); // 重新生成日历数据以确保状态正确
        } catch (error) {
          console.error('JSON处理出错:', error);
        }
        this.holidaySettingVisible = false;
      }
    },
    // 保存前端新增和删除的数据到后端
    saveSelectedDate() {
      let addData = {};
      let removeData = {};
      try {
        addData = JSON.parse(this.needAddHolidaySettingsJson || '{}');
      } catch (e) {
        addData = {};
      }
      try {
        removeData = JSON.parse(this.needRemoveHolidaySettingsJson || '{}');
      } catch (e) {
        removeData = {};
      }
      if (Object.keys(addData).length === 0 && Object.keys(removeData).length === 0) {
        this.$message.warning('没有需要保存的假期数据');
        return;
      }
      const params = {
        // 根据实际接口,你可以将新增和删除数据一起传递,或者分别调用接口
        holidaySettingsToAdd: JSON.stringify(addData),
        holidaySettingsToRemove: JSON.stringify(removeData)
      };
      addHoliday_calendar(params).then((res) => {
        if (res.code === 200) {
          this.$message.success('假期日历保存成功!');
          // 保存成功后,合并新增数据到后端数据中,并清空新增和删除数据
          const merged = { ...JSON.parse(this.holidaySettingsJson || '{}'), ...addData };
          // 删除标记的数据
          Object.keys(removeData).forEach(date => {
            delete merged[date];
          });
          this.holidaySettingsJson = JSON.stringify(merged);
          this.needAddHolidaySettingsJson = '{}';
          this.needRemoveHolidaySettingsJson = '{}';
          this.generateCalendarData();
        } else {
          this.$message.error('假期日历保存失败!');
        }
      });
    },
    // 一键删除所有假期(仅调用删除接口,删除后刷新数据)
    deleteAllDate() {
      this.$confirm('确定要清空所有节假日吗?此操作将清空所有目前已有的假期数据', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteHoliday_calendar().then(res => {
          if (res.code === 200) {
            this.$message.success('已清空所有节假日!');
            this.holidaySettingsJson = '{}';
            this.needAddHolidaySettingsJson = '{}';
            this.needRemoveHolidaySettingsJson = '{}';
            this.generateCalendarData();
          } else {
            this.$message.error('清空失败!');
          }
        });
      }).catch(() => {
        this.$message.info('已取消清空操作');
      });
    },
    // 解释说明
    remark() {
      this.$confirm('如果想在日期右上角显示"班",请在假期类型管理添加假期名称为调休, 补班, 加班这三种的任意一种,其他种类假期类型默认右上角显示"休"', '提示', {
        type: 'warning'
      })
    }
  }
};
</script>

<style scoped>
.calendar-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 10px;
  overflow-y: auto;
  max-height: 75vh;
}
.calendar {
  width: calc(100% / 4 - 10px);
  margin: 5px;
  border: 1px solid #ccc;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.calendar-header {
  height: 35px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: bold;
  color: #fff;
  background-color: #3370ff;
  padding: 5px;
}
.calendar-top {
  width: 100%;
  height: 50px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  padding-left: 10px;
}
select {
  width: 120px;
  height: 30px;
  margin-left: 10px;
  border: 1px solid #dcdfe6;
  border-radius: 3px;
}
.save-btn {
  margin-left: 10px;
}
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
  padding: 10px;
}
.calendar-day-header {
  text-align: center;
  font-weight: bold;
}
.calendar-day {
  text-align: center;
  padding: 5px;
  position: relative;
  cursor: pointer;
}
.status-label {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 10px;
}
.status-label-work {
  color: red;
}
.status-label-holiday {
  color: green;
}
</style>

其中holiday_calendar.js的代码如下所示,大家可以根据自己的实际业务自己修改

import request from '@/utils/request'

// 查询假期日历
export function holiday_calendar(query) {
  return request({
    url: '/attendance_manger/holiday_calendar/list',
    method: 'get',
    params: query
  })
}

// 保存假期日历
export function addHoliday_calendar(data) {
  return request({
    url: '/attendance_manger/holiday_calendar',
    method: 'post',
    data: data
  })
}

// 一键删除假期日历
export function deleteHoliday_calendar() {
  return request({
    url: '/attendance_manger/holiday_calendar',
    method: 'delete',
  })
}

其中holiday_type.js的代码如下所示,大家可以根据自己的实际业务自己修改

import request from '@/utils/request'

// 查询假期类型管理列表
export function listHoliday_type(query) {
  return request({
    url: '/attendance_manger/holiday_type/list',
    method: 'get',
    params: query
  })
}

其余的后端接口的话  大家根据前端自主实现吧   前端通过两个json字符串来向后端发起新增以及删除某一天假日的请求,后端只需要接受解析相应的json然后存到数据库中就行了,本文主要分享一下前端的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值