|
|
@@ -17,21 +17,14 @@
|
|
|
<el-checkbox label="workday">显示调休上班</el-checkbox>
|
|
|
</el-checkbox-group>
|
|
|
</div>
|
|
|
+
|
|
|
<!-- 单个设置按钮 -->
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- plain
|
|
|
- @click="openSingleForm('create')"
|
|
|
- >
|
|
|
+ <el-button type="primary" plain @click="openSingleForm('create')">
|
|
|
<Icon icon="ep:plus" class="mr-5px" /> 设置节假日
|
|
|
</el-button>
|
|
|
|
|
|
<!-- 批量设置按钮 -->
|
|
|
- <el-button
|
|
|
- type="success"
|
|
|
- plain
|
|
|
- @click="openBatchForm('create')"
|
|
|
- >
|
|
|
+ <el-button type="success" plain @click="openBatchForm('create')">
|
|
|
<Icon icon="ep:plus" class="mr-5px" /> 批量设置节假日
|
|
|
</el-button>
|
|
|
</ContentWrap>
|
|
|
@@ -42,8 +35,10 @@
|
|
|
<div class="calendar-day" :class="getDayClasses(data.day)">
|
|
|
<div class="day-number">
|
|
|
{{ getDayNumber(data.day) }}
|
|
|
- <!-- 如果是正常工作日显示圆圈“班” -->
|
|
|
+ <!-- ✅ 正常工作日“班” -->
|
|
|
<span v-if="isNormalWorkday(data.day)" class="work-circle">班</span>
|
|
|
+ <!-- ✅ 自定义假期“假” -->
|
|
|
+ <span v-if="isCustomHoliday(data.day)" class="custom-holiday-circle">假</span>
|
|
|
</div>
|
|
|
<div class="day-info">
|
|
|
<div class="holiday-name" v-if="getHolidayName(data.day)">
|
|
|
@@ -66,196 +61,184 @@
|
|
|
</template>
|
|
|
</el-calendar>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 子表单组件 -->
|
|
|
+ <SingleHolidayForm ref="singleFormRef" @success="refresh" />
|
|
|
+ <BatchHolidayForm ref="batchFormRef" @success="refresh" />
|
|
|
</div>
|
|
|
- <!-- 引入子表单组件 -->
|
|
|
- <SingleHolidayForm ref="singleFormRef" @success="refresh" />
|
|
|
- <BatchHolidayForm ref="batchFormRef" @success="refresh" />
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted, watch } from 'vue';
|
|
|
-import axios from 'axios';
|
|
|
-// 引入子组件
|
|
|
+import { ref, computed, onMounted, watch } from 'vue'
|
|
|
+import axios from 'axios'
|
|
|
import SingleHolidayForm from './SingleHolidayForm.vue'
|
|
|
import BatchHolidayForm from './BatchHolidayForm.vue'
|
|
|
-// 当前年
|
|
|
-const currentYear = new Date().getFullYear();
|
|
|
-const selectedYear = ref(currentYear);
|
|
|
-const selectedDate = ref(new Date());
|
|
|
-const holidayData = ref({});
|
|
|
-const showOptions = ref(['weekend', 'legal', 'holiday', 'workday']);
|
|
|
-// ref
|
|
|
+import * as HolidayApi from '@/api/calendar/index'
|
|
|
+import {getHolidayList} from "@/api/calendar/index";
|
|
|
+
|
|
|
+const currentYear = new Date().getFullYear()
|
|
|
+const selectedYear = ref(currentYear)
|
|
|
+const selectedDate = ref(new Date())
|
|
|
+const holidayData = ref({})
|
|
|
+const customHolidays = ref([]) // ✅ 自定义“假”
|
|
|
+
|
|
|
+const showOptions = ref(['weekend', 'legal', 'holiday', 'workday'])
|
|
|
+
|
|
|
const singleFormRef = ref()
|
|
|
const batchFormRef = ref()
|
|
|
|
|
|
-// 打开
|
|
|
const openSingleForm = (type) => {
|
|
|
singleFormRef.value.open(type)
|
|
|
}
|
|
|
-
|
|
|
const openBatchForm = (type) => {
|
|
|
batchFormRef.value.open(type)
|
|
|
}
|
|
|
-
|
|
|
-// 成功回调
|
|
|
-const refresh = () => {
|
|
|
- console.log('重新拉列表或做后续操作')
|
|
|
+const refresh = async () => {
|
|
|
+ console.log('刷新自定义假期')
|
|
|
+ await fetchCustomHolidays()
|
|
|
}
|
|
|
-// 年份选项
|
|
|
+
|
|
|
const yearOptions = computed(() => {
|
|
|
- const years = [];
|
|
|
- for (let year = currentYear; year <= 2050; year++) {
|
|
|
- years.push(year);
|
|
|
+ const years = []
|
|
|
+ for (let y = currentYear; y <= 2050; y++) {
|
|
|
+ years.push(y)
|
|
|
}
|
|
|
- return years;
|
|
|
-});
|
|
|
+ return years
|
|
|
+})
|
|
|
|
|
|
-// 拉节假日接口,失败则用默认
|
|
|
+// 获取第三方节假日
|
|
|
const fetchHolidayData = async (year) => {
|
|
|
try {
|
|
|
- const res = await axios.get(`https://fastly.jsdelivr.net/gh/NateScarlet/holiday-cn@master/${year}.json`);
|
|
|
- const days = res.data?.days || [];
|
|
|
- if (Array.isArray(days) && days.length > 0) {
|
|
|
- const dayMap = {};
|
|
|
- days.forEach(item => {
|
|
|
- dayMap[item.date] = {
|
|
|
- name: item.name,
|
|
|
- isOffDay: item.isOffDay,
|
|
|
- isLegal: item.isOffDay // 简单视为法定
|
|
|
- };
|
|
|
- });
|
|
|
- holidayData.value[year] = dayMap;
|
|
|
- } else {
|
|
|
- console.warn(`无 ${year} 节假日数据,用默认`);
|
|
|
- holidayData.value[year] = generateDefaultHolidays(year);
|
|
|
- }
|
|
|
+ const res = await axios.get(`https://fastly.jsdelivr.net/gh/NateScarlet/holiday-cn@master/${year}.json`)
|
|
|
+ const days = res.data?.days || []
|
|
|
+ const dayMap = {}
|
|
|
+ days.forEach(item => {
|
|
|
+ dayMap[item.date] = {
|
|
|
+ name: item.name,
|
|
|
+ isOffDay: item.isOffDay,
|
|
|
+ isLegal: item.isOffDay
|
|
|
+ }
|
|
|
+ })
|
|
|
+ holidayData.value[year] = dayMap
|
|
|
} catch (err) {
|
|
|
- console.error(`获取 ${year} 节假日失败,使用默认`, err);
|
|
|
- holidayData.value[year] = generateDefaultHolidays(year);
|
|
|
+ console.error(`获取 ${year} 节假日失败`, err)
|
|
|
+ holidayData.value[year] = {}
|
|
|
}
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 获取当前日历渲染月份的年月字符串(格式:YYYY-MM)
|
|
|
+ * @returns 格式化后的年月字符串
|
|
|
+ */
|
|
|
+const getCurrentMonth = () => {
|
|
|
+ const year = selectedDate.value.getFullYear();
|
|
|
+ // 月份从0开始,需+1并补0
|
|
|
+ const month = String(selectedDate.value.getMonth() + 1).padStart(2, '0');
|
|
|
+ return `${year}-${month}`;
|
|
|
};
|
|
|
-
|
|
|
-// 默认假期
|
|
|
-const generateDefaultHolidays = (year) => {
|
|
|
- const holidays = {};
|
|
|
- holidays[`${year}-01-01`] = { name: '元旦', isOffDay: true, isLegal: true };
|
|
|
- for (let i = 0; i < 7; i++) {
|
|
|
- const date = new Date(year, 0, 22 + i);
|
|
|
- holidays[formatDateKey(date)] = { name: '春节', isOffDay: true, isLegal: i < 3 };
|
|
|
- }
|
|
|
- holidays[`${year}-04-04`] = { name: '清明节', isOffDay: true, isLegal: true };
|
|
|
- for (let i = 0; i < 3; i++) {
|
|
|
- holidays[`${year}-05-0${1 + i}`] = { name: '劳动节', isOffDay: true, isLegal: i === 0 };
|
|
|
- }
|
|
|
- holidays[`${year}-09-29`] = { name: '中秋节', isOffDay: true, isLegal: true };
|
|
|
- for (let i = 1; i <= 7; i++) {
|
|
|
- holidays[`${year}-10-${i < 10 ? '0' + i : i}`] = {
|
|
|
- name: '国庆节',
|
|
|
- isOffDay: true,
|
|
|
- isLegal: i <= 3
|
|
|
- };
|
|
|
- }
|
|
|
- return holidays;
|
|
|
-};
|
|
|
-
|
|
|
-// 工具
|
|
|
+// ✅ 拉自定义假期
|
|
|
+const fetchCustomHolidays = async () => {
|
|
|
+ const yearMonth = getCurrentMonth();
|
|
|
+ const res = await HolidayApi.getHolidayList({ yearMonth })
|
|
|
+ console.log(res, '自定义假期')
|
|
|
+ customHolidays.value = Array.isArray(res) ? res : []
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => selectedDate.value,
|
|
|
+ (newDate, oldDate) => {
|
|
|
+ if (newDate) {
|
|
|
+ fetchCustomHolidays();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true } // 初始加载时立即执行一次
|
|
|
+);
|
|
|
const formatDateKey = (date) => {
|
|
|
- const y = date.getFullYear();
|
|
|
- const m = date.getMonth() + 1;
|
|
|
- const d = date.getDate();
|
|
|
- return `${y}-${m < 10 ? '0' + m : m}-${d < 10 ? '0' + d : d}`;
|
|
|
-};
|
|
|
+ const y = date.getFullYear()
|
|
|
+ const m = date.getMonth() + 1
|
|
|
+ const d = date.getDate()
|
|
|
+ return `${y}-${m < 10 ? '0' + m : m}-${d < 10 ? '0' + d : d}`
|
|
|
+}
|
|
|
|
|
|
const isWeekend = (dateStr) => {
|
|
|
- const d = new Date(dateStr).getDay();
|
|
|
- return d === 0 || d === 6;
|
|
|
-};
|
|
|
+ const d = new Date(dateStr).getDay()
|
|
|
+ return d === 0 || d === 6
|
|
|
+}
|
|
|
const isLegalHoliday = (dateStr) => {
|
|
|
- const k = formatDateKey(new Date(dateStr));
|
|
|
- return holidayData.value[selectedYear.value]?.[k]?.isLegal || false;
|
|
|
-};
|
|
|
+ const k = formatDateKey(new Date(dateStr))
|
|
|
+ return holidayData.value[selectedYear.value]?.[k]?.isLegal || false
|
|
|
+}
|
|
|
const isHoliday = (dateStr) => {
|
|
|
- const k = formatDateKey(new Date(dateStr));
|
|
|
- return holidayData.value[selectedYear.value]?.[k]?.isOffDay || false;
|
|
|
-};
|
|
|
+ const k = formatDateKey(new Date(dateStr))
|
|
|
+ return holidayData.value[selectedYear.value]?.[k]?.isOffDay || false
|
|
|
+}
|
|
|
const isWorkday = (dateStr) => {
|
|
|
- const k = formatDateKey(new Date(dateStr));
|
|
|
- const isWeekendDay = isWeekend(dateStr);
|
|
|
- const isHolidayDay = holidayData.value[selectedYear.value]?.[k]?.isOffDay || false;
|
|
|
- return isWeekendDay && !isHolidayDay;
|
|
|
-};
|
|
|
-// ✅ 新增:正常工作日
|
|
|
+ const k = formatDateKey(new Date(dateStr))
|
|
|
+ return isWeekend(dateStr) && !holidayData.value[selectedYear.value]?.[k]?.isOffDay
|
|
|
+}
|
|
|
const isNormalWorkday = (dateStr) => {
|
|
|
- return (
|
|
|
- !isWeekend(dateStr) &&
|
|
|
- !isLegalHoliday(dateStr) &&
|
|
|
- !isHoliday(dateStr)
|
|
|
- );
|
|
|
-};
|
|
|
+ return !isWeekend(dateStr) && !isLegalHoliday(dateStr) && !isHoliday(dateStr)
|
|
|
+}
|
|
|
+
|
|
|
+// ✅ 判断自定义假期
|
|
|
+const isCustomHoliday = (dateStr) => {
|
|
|
+ const k = formatDateKey(new Date(dateStr))
|
|
|
+ console.log('🔍 当前单元格:', dateStr, '格式化后:', k)
|
|
|
+ console.log('🔍 后端自定义假期:', customHolidays.value.map(x => x.theDay))
|
|
|
+ return customHolidays.value.some(item => item.theDay === k && item.holidayType === 'HOLIDAY')
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
const getDayClasses = (dateStr) => {
|
|
|
- const c = [];
|
|
|
- if (isWeekend(dateStr)) c.push('weekend');
|
|
|
- if (isLegalHoliday(dateStr)) c.push('legal-holiday');
|
|
|
- if (isHoliday(dateStr)) c.push('holiday');
|
|
|
- if (isWorkday(dateStr)) c.push('workday');
|
|
|
- return c;
|
|
|
-};
|
|
|
-const getDayNumber = (dateStr) => new Date(dateStr).getDate();
|
|
|
+ const c = []
|
|
|
+ if (isWeekend(dateStr)) c.push('weekend')
|
|
|
+ if (isLegalHoliday(dateStr)) c.push('legal-holiday')
|
|
|
+ if (isHoliday(dateStr)) c.push('holiday')
|
|
|
+ if (isWorkday(dateStr)) c.push('workday')
|
|
|
+ return c
|
|
|
+}
|
|
|
+
|
|
|
+const getDayNumber = (dateStr) => new Date(dateStr).getDate()
|
|
|
const getHolidayName = (dateStr) => {
|
|
|
- const k = formatDateKey(new Date(dateStr));
|
|
|
- return holidayData.value[selectedYear.value]?.[k]?.name || '';
|
|
|
-};
|
|
|
+ const k = formatDateKey(new Date(dateStr))
|
|
|
+ return holidayData.value[selectedYear.value]?.[k]?.name || ''
|
|
|
+}
|
|
|
|
|
|
const handleYearChange = (year) => {
|
|
|
- selectedDate.value = new Date(year, 0, 1);
|
|
|
- if (!holidayData.value[year]) {
|
|
|
- fetchHolidayData(year);
|
|
|
- }
|
|
|
-};
|
|
|
+ selectedDate.value = new Date(year, 0, 1)
|
|
|
+ if (!holidayData.value[year]) fetchHolidayData(year)
|
|
|
+}
|
|
|
|
|
|
-// 监听跨年
|
|
|
-watch(selectedDate, (newDate) => {
|
|
|
- const newYear = newDate.getFullYear();
|
|
|
+watch(selectedDate, async (newDate) => {
|
|
|
+ const newYear = newDate.getFullYear()
|
|
|
if (newYear !== selectedYear.value) {
|
|
|
- selectedYear.value = newYear;
|
|
|
- if (!holidayData.value[newYear]) {
|
|
|
- fetchHolidayData(newYear);
|
|
|
- }
|
|
|
+ selectedYear.value = newYear
|
|
|
+ if (!holidayData.value[newYear]) await fetchHolidayData(newYear)
|
|
|
}
|
|
|
-});
|
|
|
+})
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- fetchHolidayData(currentYear);
|
|
|
-});
|
|
|
+onMounted(async () => {
|
|
|
+ await fetchHolidayData(currentYear)
|
|
|
+ await fetchCustomHolidays()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.holiday-calendar {
|
|
|
width: 100%;
|
|
|
}
|
|
|
-
|
|
|
.controls {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
align-items: center;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
-
|
|
|
.calendar-wrapper {
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
-
|
|
|
-.el-calendar {
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
-
|
|
|
.calendar-day {
|
|
|
height: 100%;
|
|
|
padding: 5px;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
-
|
|
|
.day-number {
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 5px;
|
|
|
@@ -263,7 +246,6 @@ onMounted(() => {
|
|
|
align-items: center;
|
|
|
gap: 4px;
|
|
|
}
|
|
|
-
|
|
|
.work-circle {
|
|
|
display: inline-block;
|
|
|
background: #409EFF;
|
|
|
@@ -275,33 +257,37 @@ onMounted(() => {
|
|
|
line-height: 18px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
-
|
|
|
+.custom-holiday-circle {
|
|
|
+ display: inline-block;
|
|
|
+ background: orange;
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 10px;
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ line-height: 18px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
.day-info {
|
|
|
min-height: 20px;
|
|
|
}
|
|
|
-
|
|
|
.holiday-name {
|
|
|
font-size: 12px;
|
|
|
color: #f56c6c;
|
|
|
margin-bottom: 2px;
|
|
|
}
|
|
|
-
|
|
|
.el-tag {
|
|
|
margin: 2px;
|
|
|
}
|
|
|
-
|
|
|
.weekend {
|
|
|
background-color: #f0f9ff;
|
|
|
}
|
|
|
-
|
|
|
.legal-holiday {
|
|
|
background-color: #fff0f0;
|
|
|
}
|
|
|
-
|
|
|
.holiday {
|
|
|
background-color: #f0fff0;
|
|
|
}
|
|
|
-
|
|
|
.workday {
|
|
|
background-color: #fffaf0;
|
|
|
}
|