案例图:
代码:
<template>
<el-dialog
:title="dialogTitle"
:visible.sync="showCalendar"
width="27%"
@close="handleClose"
>
<div class="custom-calendar">
<!-- 月份切换区域 -->
<div class="calendar-header">
<el-button icon="el-icon-arrow-left" plain @click="prevMonth">
</el-button>
<span>{{ currentYear }}年{{ currentMonth }}月</span>
<el-button icon="el-icon-arrow-right" plain @click="nextMonth">
</el-button>
</div>
<!-- 星期标题区域 -->
<div class="weekdays">
<div
v-for="(weekday, index) in weekdays"
:key="index"
class="weekday"
>
{{ weekday }}
</div>
</div>
<!-- 日期单元格区域 -->
<div class="days-grid">
<!-- 填充空白日期 -->
<div
v-for="(empty, index) in firstDayOffset"
:key="'empty-' + index"
class="day empty"
>
</div>
<!-- 遍历展示当前月日期 -->
<div
v-for="(dateObj, index) in visibleDates"
:key="index"
class="day"
:class="{ 'selected': isSelected(dateObj.dateStr) }"
@click="toggleSelection(dateObj.dateStr)"
>
{{ dateObj.day }}
</div>
</div>
<!-- 底部按钮 -->
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">
{{ console.log('点击取消按钮,执行 cancel 方法') }}
取消
</el-button>
<el-button type="primary" @click="confirm">
{{ console.log('点击确认按钮,执行 confirm 方法') }}
确定
</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
export default (await import('vue')).defineComponent({
name: 'CustomCalendar',
// 从父组件接收 dialogTitle,用于动态设置 el-dialog 的 title
props: {
dialogTitle: {
type: String,
default: '设置工厂日历'
},
customData: {
type: Object,
default: () => ({})
}
},
data() {
return {
getrowData: null, //父组件-选中行的数据
showCalendar: false,
currentYear: new Date().getFullYear(), //显示当前的年份
currentMonth: new Date().getMonth() + 1, //显示当前的月份
selectedDates: [], //存储选中的日期,格式为'YYYY-MM-DD',用于渲染
cancelDatas: [], //从接口获取 上一次存入的值(用于-最终生成取消选中的数据,作为参数传递)
oldSelectDate: [], //从接口获取 上一次存入的值且过滤出当钱日期之前的日期值,(目的:选择日期时,需要判断是否时上一次选择过的,如果时上一次选择的日期,不可取消选中)
weekdays: ['日', '一', '二', '三', '四', '五', '六']
}
},
computed: {
// 计算当前月要显示的日期数据
visibleDates() {
console.log('执行 visibleDates 计算属性');
const dates = [];
const firstDay = new Date(this.currentYear, this.currentMonth - 1, 1);
const lastDay = new Date(this.currentYear, this.currentMonth, 0).getDate();
for (let i = 1; i <= lastDay; i++) {
const date = new Date(this.currentYear, this.currentMonth - 1, i);
dates.push({
dateStr: this.formatDate(date),
day: i
});
}
return dates;
},
// 计算当前月第一天是星期几,用于填充空白日期
firstDayOffset() {
console.log('执行 firstDayOffset 计算属性');
const firstDay = new Date(this.currentYear, this.currentMonth - 1, 1);
return firstDay.getDay();
}
},
watch: {
customData: {
handler(newValue, oldVal) {
console.log('监听 customData 变化,newValue===', newValue, 'oldVal===', oldVal);
if (newValue) {
this.selectedDates = newValue.workDate || [];
this.getrowData = newValue.rowData;
}
},
deep: true,
immediate: true
}
},
methods: {
// 弹窗右上角--关闭
handleClose() {
this.selectedDates = [];
this.cancelDatas = [];
this.oldSelectDate = [];
this.showCalendar = false;
},
// 格式化日期为 'YYYY - MM - DD' 格式
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 判断日期是否已选中
isSelected(dateStr) {
return this.selectedDates.includes(dateStr);
},
// 切换日期选中状态
toggleSelection(dateStr) {
console.log('执行 toggleSelection 方法,切换日期:', dateStr);
debugger;
if (this.oldSelectDate.includes(dateStr)) {
console.log('上次已选择过的值==', dateStr);
return;
}
if (this.isSelected(dateStr)) {
// 取消选中
console.log('取消选中日期:', dateStr);
this.cancelDatas.push(dateStr);
this.selectedDates = this.selectedDates.filter((item) => item !== dateStr);
} else {
// 选中日期
console.log('选中日期:', dateStr);
this.selectedDates.push(dateStr);
}
},
// 切换到上月
prevMonth() {
if (this.currentMonth === 1) {
this.currentYear--;
this.currentMonth = 12;
} else {
this.currentMonth--;
}
// 切换月份后清空已选中日期(可根据需求决定是否保留,目前是保留)
// this.selectedDates = [];
},
// 切换到下月
nextMonth() {
if (this.currentMonth === 12) {
this.currentYear++;
this.currentMonth = 1;
} else {
this.currentMonth++;
}
// 切换月份后清空已选中日期(可根据需求决定是否保留,目前是保留)
// this.selectedDates = [];
},
// 取消按钮
cancel() {
this.selectedDates = [];
this.cancelDatas = [];
this.oldSelectDate = [];
this.showCalendar = false;
this.$emit('close', false);
},
// 确定,将值传入父组件
confirm() {
console.log('执行 confirm 方法,最终选中的日期:', this.selectedDates);
// 过滤 selectedDates,只保留和 oldSelectDate 不相等的日期
const filteredSelectedDates = this.selectedDates.filter(
(date) => !this.oldSelectDate.includes(date)
);
const result = this.validateWorkDates(filteredSelectedDates);
if (result === true) {
console.log('所有日期都在允许范围内');
} else {
return this.$message.warning(result);
}
// 已选中的值
const newSelectedDates = this.selectedDates.map((date) => ({
operateType: 'Y',
workDate: date
}));
// 取消选中的数据
const newCancelDatas = this.cancelDatas.map((date) => ({
operateType: 'N',
workDate: date
}));
const workDates = [...newCancelDatas, ...newSelectedDates];
console.log(workDates, '==进来了==', newCancelDatas);
this.getrowData.workDate = workDates;
this.$emit('submit', this.getrowData);
this.showCalendar = false;
},
// 验证日期是否符合规则
validateWorkDates(workDate) {
console.log('执行 validateWorkDates 方法,验证日期:', workDate);
const today = new Date();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
const currentDay = today.getDate();
// 计算允许的最大月份(当前月+5)
const maxMonth = (currentMonth + 5) % 12;
const maxYear =
currentYear + Math.floor((currentMonth + 5) / 12);
// 错误信息收集
const pastDayDates = [];
const pastDates = [];
const futureDates = [];
// 遍历检查每个日期
workDate.forEach((dateStr) => {
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
return;
}
const dateYear = date.getFullYear();
const dateMonth = date.getMonth();
const dateDay = date.getDate();
// 1. 判断是否在当前之前
if (
dateYear < currentYear ||
(dateYear === currentYear && dateMonth < currentMonth)
) {
pastDates.push(dateStr);
}
// 2. 判断是否当前日期
else if (
dateYear < currentYear ||
(dateYear === currentYear && dateMonth < currentMonth) ||
(dateYear === currentYear &&
dateMonth === currentMonth &&
dateDay < currentDay)
) {
pastDayDates.push(dateStr);
}
// 判断是否超过当前月+5个月
else if (
dateYear > maxYear ||
(dateYear === maxYear && dateMonth > maxMonth)
) {
futureDates.push(dateStr);
}
});
// 生成提示信息
let message = '';
if (pastDates.length > 0 || pastDayDates.length > 0) {
message += '当前日期之前的日期不可修改设置\n';
}
if (futureDates.length > 0) {
message += '仅支持设置当前月及往后5个月的日历\n';
}
// 如果有错误,返回错误信息;否则返回 true
return message ? message : true;
}
}
});
</script>
<style scoped>
/* 日历整体样式 */
.custom-calendar {
width: 100%;
border: 1px solid #f9fafc;
background-color: #f9fafc;
border-radius: 4px;
padding: 10px;
margin: 0 auto;
font-size: 18px;
}
/* 头部样式 */
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
/* 星期标题样式 */
.weekdays {
display: flex;
margin-bottom: 10px;
}
.weekday {
flex: 1;
text-align: center;
font-weight: bold;
}
/* 日期网格样式 */
.days-grid {
display: flex;
flex-wrap: wrap;
}
/* 日期单元格基础样式 */
.day {
width: 14.28%;
height: 63px;
line-height: 63px;
text-align: center;
border: 1px solid #fffbfb;
box-sizing: border-box;
cursor: pointer;
}
/* 空白日期样式(用于填充月初前面的空白) */
.day.empty {
background-color: #f9f9f9;
cursor: default;
}
/* 选中日期样式 */
.day.selected {
background-color: #409eff;
color: white;
}
/* 底部按钮样式 */
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.dialog-footer button {
margin-left: 10px;
}
/**选中日期展示样式*/
.result{
margin-top:10px;
text-align:center
}
</style>