sh_real_name_system_web/src/components/AttendanceCalendar/index.vue

429 lines
11 KiB
Vue

<template>
<div class="attendance-calendar">
<div class="legend">
<div class="legend-item">
<span class="legend-mark active"></span>
<span>已打卡</span>
</div>
<div class="legend-item">
<span class="legend-mark inactive"></span>
<span>未打卡</span>
</div>
<div class="legend-item">
<span class="legend-mark not-in-range"></span>
<span>补卡</span>
</div>
<div class="legend-item">
<span class="legend-mark inactive-no-att"></span>
<span>不在场</span>
</div>
</div>
<!-- 循环展示每个月份的日历 -->
<div
:key="index"
class="month-calendar"
v-for="(monthData, index) in monthDataListNew"
>
<!-- 月份标题 -->
<div class="calendar-header">
<h3>{{ monthData.year }}年 {{ monthData.month }}月</h3>
</div>
<!-- 星期标题 -->
<div class="week-days">
<div class="week-day" v-for="day in weekDays" :key="day">
{{ day }}
</div>
</div>
<!-- 日历主体 -->
<div class="calendar-grid">
<div
class="calendar-day empty"
:key="'empty-start-' + i"
v-for="(empty, i) in monthData.emptyStartDays"
>
</div>
<div
:key="day.dayIndex"
:class="getDayClass(day)"
class="calendar-day current-month"
v-for="day in monthData.currentMonthDays"
>
<span class="day-number">{{ day.dayIndex }}</span>
</div>
<div
:key="'empty-end-' + i"
class="calendar-day empty"
v-for="(empty, i) in monthData.emptyEndDays"
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'AttendanceCalendar',
props: {
// 时间范围
timeRange: {
type: Array,
default: () => [],
},
// 考勤列表
attendanceList: {
type: Array,
default: () => [],
},
},
data() {
return {
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
checkedDays: {}, // 存储复选框状态
checked: false,
monthDataListNew: [], // 日历的月份数据
}
},
methods: {
// 初始化复选框状态
initCheckedDays() {
this.monthDataList.forEach((monthData) => {
monthData.day.forEach((day) => {
if (day.isActive === 1) {
const key = `${monthData.year}-${monthData.month}-${day.dayIndex}`
this.$set(this.checkedDays, key, false)
}
})
})
},
// 初始化日历
initCalendar(timeRange) {
const startMonth = timeRange[0].split('-')[1]
const endMonth = timeRange[1].split('-')[1]
const year = timeRange[0].split('-')[0]
this.monthDataListNew = []
if (startMonth !== endMonth) {
this.monthDataListNew.push({
month: startMonth,
year,
emptyStartDays: this.getFirstMonthEmptyDays({
month: startMonth,
year,
}),
emptyEndDays: this.getLastMonthEmptyDays({
month: startMonth,
year,
}),
currentMonthDays: this.getDaysInMonthNew({
month: startMonth,
year,
}),
})
this.monthDataListNew.push({
month: endMonth,
year,
emptyStartDays: this.getFirstMonthEmptyDays({
month: endMonth,
year,
}),
emptyEndDays: this.getLastMonthEmptyDays({
month: endMonth,
year,
}),
currentMonthDays: this.getDaysInMonthNew({
month: endMonth,
year,
}),
})
} else {
this.monthDataListNew.push({
month: startMonth,
year,
emptyStartDays: this.getFirstMonthEmptyDays({
month: startMonth,
year,
}),
emptyEndDays: this.getLastMonthEmptyDays({
month: startMonth,
year,
}),
currentMonthDays: this.getDaysInMonthNew({
month: startMonth,
year,
}),
})
}
},
// 设置考勤列表信息
initAttendanceList(attendanceList) {
if (attendanceList.length > 0) {
attendanceList.forEach((item) => {
const year = item.einDay.split('-')[0]
const month = item.einDay.split('-')[1]
this.monthDataListNew.forEach((j) => {
if (j.year == year && j.month == month) {
j.currentMonthDays.forEach((k) => {
if (k.zeroIndex == item.einDay.split('-')[2]) {
k.isAtt = item.isAtt
k.isRepair = item.isRepair
}
})
}
})
})
}
},
// 获取指定月份的天数
getDaysInMonth(monthData) {
const year = parseInt(monthData.year)
const month = parseInt(monthData.month)
const days = new Date(year, month, 0).getDate()
return days
},
getDaysInMonthNew(monthData) {
const year = parseInt(monthData.year)
const month = parseInt(monthData.month)
const days = new Date(year, month, 0).getDate()
const dayList = []
for (let i = 1; i <= days * 1; i++) {
dayList.push({
dayIndex: i,
isAtt: null,
isRepair: false,
zeroIndex: i > 10 ? i : `0${i}`,
})
}
return dayList
},
// 获取月份第一天是星期几
getFirstDayOfMonth(monthData) {
const year = parseInt(monthData.year)
const month = parseInt(monthData.month) - 1 // 月份是0-based
return new Date(year, month, 1).getDay()
},
// 计算月初需要的空白格子数量
getFirstMonthEmptyDays(monthData) {
return this.getFirstDayOfMonth(monthData)
},
// 计算月末需要的空白格子数量
getLastMonthEmptyDays(monthData) {
console.log(monthData, 'monthData----')
const totalDays = this.getDaysInMonth(monthData)
const lastDay = new Date(
parseInt(monthData.year),
parseInt(monthData.month) - 1,
totalDays,
).getDay()
return 6 - lastDay
},
// 获取某天的样式类
getDayClass(day) {
switch (day.isAtt) {
case 0:
return 'inactive'
case 1:
return 'active'
case 2:
return 'active-repair'
}
return 'is-no-att'
},
},
watch: {
timeRange: {
handler(newVal) {
console.log(newVal, 'newVal')
this.initCalendar(newVal)
},
deep: true,
immediate: true,
},
attendanceList: {
handler(newVal) {
this.initAttendanceList(newVal)
},
deep: true,
},
},
}
</script>
<style scoped>
.attendance-calendar {
font-family: 'Arial', sans-serif;
max-width: 90%;
min-width: 460px;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.month-calendar {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #f0f0f0;
}
.month-calendar:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.calendar-header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
}
.calendar-header h3 {
margin: 0;
color: #333;
font-size: 18px;
}
.week-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
margin-bottom: 10px;
}
.week-day {
text-align: center;
font-weight: bold;
color: #666;
padding: 2px 0;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
position: relative;
font-size: 14px;
}
.empty {
background-color: #f9f9f9;
border: 1px solid transparent;
}
.current-month {
border: 1px solid #eee;
cursor: pointer;
}
/* 非考勤范围样式 */
.not-in-range {
background-color: #f0f0f0;
color: #999;
}
/* 已打卡样式 */
.active {
background-color: #19be6b;
color: #000;
}
/* 未打卡样式 */
.inactive {
background-color: #f56c6c;
color: #000;
}
/* 已补卡样式 */
.active-repair {
background-color: #ff9900;
color: #000;
}
/* 不在场样式 */
.inactive-no-att {
background-color: #999;
color: #000;
}
.day-number {
z-index: 1;
}
/* 复选框样式 */
.day-checkbox {
position: absolute;
top: 2px;
right: 2px;
z-index: 2;
}
/* 图例样式 */
.legend {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
color: #666;
}
.legend-mark {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 3px;
}
.legend-mark.active {
background-color: #19be6b;
}
.legend-mark.inactive {
background-color: #f56c6c;
}
.legend-mark.not-in-range {
background-color: #ff9900;
}
.is-no-att {
background-color: #999 !important;
}
</style>