开发要求:需要展示当前月份及前面月份的日期表,即6月-7月,前端使用的是vue+element plus
时间: 2025-07-21 22:53:31 浏览: 6
<think>我们需要展示一个包含当前月份及前一个月份的日期表(例如当前是7月,则显示6月和7月)。我们可以使用Element Plus的日历组件(`el-calendar`)或者自己构建一个简单的月份视图。但是,由于需要展示两个月份,并且可能要求以表格形式展示,我们可以考虑使用两个`el-calendar`并排,或者自己构建一个自定义的月份表格。
由于`el-calendar`默认一次只显示一个月,所以我们可以使用两个`el-calendar`组件来分别显示上个月和当前月。
但是,根据需求,我们只需要展示两个月的日期,并且可能希望以更紧凑的方式(比如去掉不需要的头部和尾部),我们可以自己构建一个简单的月份表格。
这里我选择自己构建,因为这样更灵活,可以控制样式和布局。
思路:
1. 获取当前月份和上一个月份的信息(包括每个月的第一天是星期几,每个月的总天数)。
2. 构建两个数组,分别表示两个月的日期(包括上个月在本月显示的部分和下个月在本月显示的部分,类似于日历的完整视图)。
3. 使用表格展示这两个月,每个月份用一个表格表示,并排显示。
步骤:
- 计算当前月份和上一个月的日期数据。
- 构建两个月的日历数组(长度为42,因为一个月最多6周,7天*6=42格)。
- 使用两个表格分别渲染这两个月的日历。
注意:我们要求只显示公历日期,不需要农历。
代码实现:
我们将创建一个组件,使用两个表格展示两个月的日历。
由于需要展示月份的名称,我们在表格上方显示月份标题。
我们将使用Vue3的Composition API。
下面是具体代码:</think># 月份日期表组件 - 展示当前月及前一个月的日期
下面是一个完整的Vue + Element Plus实现,用于展示当前月份及前一个月的日期表:
```vue
<template>
<div class="month-calendar-container">
<el-card class="calendar-card">
<div class="header">
<h1>月份日期表</h1>
<p>展示当前月份及前一个月的日期</p>
</div>
<div class="calendar-controls">
<el-button @click="prevMonths" icon="el-icon-arrow-left" circle></el-button>
<el-button type="primary" @click="resetToCurrent" plain>返回当前月份</el-button>
<el-button @click="nextMonths" icon="el-icon-arrow-right" circle></el-button>
</div>
<div class="month-display">
<div class="month-title">
{{ formatMonth(prevMonth) }}
</div>
<div class="month-title">
{{ formatMonth(currentMonth) }}
</div>
</div>
<div class="calendar-grid">
<!-- 前一个月 -->
<div class="month-container">
<div class="weekdays">
<div v-for="day in weekDays" :key="day" class="weekday">{{ day }}</div>
</div>
<div class="days-grid">
<div
v-for="(day, index) in prevMonthDays"
:key="'prev-' + index"
:class="[
'day-cell',
{ 'current-month': day.isCurrentMonth, 'today': day.isToday, 'weekend': day.isWeekend }
]"
>
<div class="day-number">{{ day.day }}</div>
<div v-if="day.isToday" class="today-badge">今天</div>
<div v-if="day.events.length" class="events-indicator">
<el-badge :value="day.events.length" type="primary" />
</div>
</div>
</div>
</div>
<!-- 当前月 -->
<div class="month-container">
<div class="weekdays">
<div v-for="day in weekDays" :key="day" class="weekday">{{ day }}</div>
</div>
<div class="days-grid">
<div
v-for="(day, index) in currentMonthDays"
:key="'current-' + index"
:class="[
'day-cell',
{ 'current-month': day.isCurrentMonth, 'today': day.isToday, 'weekend': day.isWeekend }
]"
>
<div class="day-number">{{ day.day }}</div>
<div v-if="day.isToday" class="today-badge">今天</div>
<div v-if="day.events.length" class="events-indicator">
<el-badge :value="day.events.length" type="primary" />
</div>
</div>
</div>
</div>
</div>
<div class="legend">
<div class="legend-item">
<div class="color-box today"></div>
<span>今天</span>
</div>
<div class="legend-item">
<div class="color-box weekend"></div>
<span>周末</span>
</div>
<div class="legend-item">
<div class="color-box event"></div>
<span>有事件</span>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue';
import { format, startOfMonth, endOfMonth, eachDayOfInterval,
addMonths, subMonths, isSameDay, isToday, isWeekend } from 'date-fns';
import { zhCN } from 'date-fns/locale';
export default {
setup() {
const currentDate = ref(new Date());
const weekDays = ref(['日', '一', '二', '三', '四', '五', '六']);
// 计算前一个月和当前月的日期范围
const prevMonth = computed(() => subMonths(currentDate.value, 1));
const currentMonth = computed(() => currentDate.value);
// 格式化月份显示
const formatMonth = (date) => {
return format(date, 'yyyy年MM月', { locale: zhCN });
};
// 生成日期数据
const generateMonthDays = (month) => {
const start = startOfMonth(month);
const end = endOfMonth(month);
const days = eachDayOfInterval({ start, end });
// 计算日历中需要显示的前置天数(上个月的最后几天)
const firstDayOfWeek = start.getDay();
const prevDays = [];
for (let i = 0; i < firstDayOfWeek; i++) {
const prevDate = new Date(start);
prevDate.setDate(start.getDate() - (firstDayOfWeek - i));
prevDays.push({
day: prevDate.getDate(),
date: prevDate,
isCurrentMonth: false,
isToday: isToday(prevDate),
isWeekend: isWeekend(prevDate),
events: []
});
}
// 当前月的日期
const currentDays = days.map(day => ({
day: day.getDate(),
date: day,
isCurrentMonth: true,
isToday: isToday(day),
isWeekend: isWeekend(day),
events: generateRandomEvents(day)
}));
// 组合所有日期
return [...prevDays, ...currentDays];
};
// 生成随机事件
const generateRandomEvents = (date) => {
const events = [];
// 30%的几率生成事件
if (Math.random() > 0.7) {
const count = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < count; i++) {
events.push({
id: `${date.getTime()}-${i}`,
title: `事件 ${i+1}`,
time: `${Math.floor(Math.random() * 24)}:00`
});
}
}
return events;
};
// 计算前一个月和当前月的日期数据
const prevMonthDays = computed(() => generateMonthDays(prevMonth.value));
const currentMonthDays = computed(() => generateMonthDays(currentMonth.value));
// 导航功能
const prevMonths = () => {
currentDate.value = subMonths(currentDate.value, 1);
};
const nextMonths = () => {
currentDate.value = addMonths(currentDate.value, 1);
};
const resetToCurrent = () => {
currentDate.value = new Date();
};
return {
weekDays,
prevMonth,
currentMonth,
prevMonthDays,
currentMonthDays,
formatMonth,
prevMonths,
nextMonths,
resetToCurrent
};
}
};
</script>
<style scoped>
.month-calendar-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.header h1 {
color: #2c3e50;
margin-bottom: 8px;
}
.header p {
color: #7f8c8d;
font-size: 16px;
}
.calendar-card {
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 25px;
}
.calendar-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-bottom: 30px;
}
.calendar-controls .el-button {
transition: all 0.3s ease;
}
.calendar-controls .el-button:hover {
transform: scale(1.05);
}
.month-display {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
padding: 0 10px;
}
.month-title {
flex: 1;
text-align: center;
font-size: 20px;
font-weight: bold;
color: #3498db;
background: linear-gradient(to right, #f8f9fa, #e9ecef);
padding: 12px 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.month-title:first-child {
margin-right: 10px;
}
.month-title:last-child {
margin-left: 10px;
}
.calendar-grid {
display: flex;
gap: 30px;
}
.month-container {
flex: 1;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
background: white;
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
background-color: #f5f7fa;
border-bottom: 1px solid #e0e0e0;
}
.weekday {
padding: 12px 0;
text-align: center;
font-weight: bold;
color: #5a6268;
}
.days-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background-color: #e0e0e0;
}
.day-cell {
min-height: 100px;
background-color: white;
padding: 8px;
position: relative;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.day-cell:hover {
background-color: #f8f9fa;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
z-index: 1;
}
.day-number {
font-size: 16px;
font-weight: 500;
color: #2c3e50;
align-self: flex-end;
}
.current-month .day-number {
color: #2c3e50;
}
.day-cell:not(.current-month) .day-number {
color: #bdc3c7;
}
.today {
background-color: #e3f2fd;
border: 2px solid #2196f3;
}
.today-badge {
position: absolute;
top: 5px;
right: 5px;
background-color: #2196f3;
color: white;
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
}
.weekend {
background-color: #fff8e1;
}
.events-indicator {
margin-top: auto;
text-align: center;
}
.legend {
display: flex;
justify-content: center;
gap: 25px;
margin-top: 25px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.color-box {
width: 20px;
height: 20px;
border-radius: 4px;
}
.color-box.today {
background-color: #e3f2fd;
border: 1px solid #2196f3;
}
.color-box.weekend {
background-color: #fff8e1;
border: 1px solid #ffc107;
}
.color-box.event {
background-color: #e3f2fd;
position: relative;
}
.color-box.event::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
background-color: #2196f3;
border-radius: 50%;
}
</style>
```
## 功能说明
这个组件实现了以下功能:
1. **双月展示**:同时展示当前月份和前一个月份的日历
2. **日期高亮**:
- 当前日期(今天)使用特殊样式标识
- 周末日期使用不同背景色
- 当前月份日期使用正常颜色,非当前月份日期使用灰色
3. **事件指示**:有事件的日期会显示事件数量标记
4. **导航功能**:
- 左右箭头按钮可向前/向后浏览月份
- "返回当前月份"按钮可快速回到当前月份
5. **响应式设计**:适配不同屏幕尺寸
6. **视觉设计**:
- 使用卡片布局提升视觉层次
- 添加阴影和过渡效果增强用户体验
- 清晰的月份标题和日期标识
- 底部的图例说明
## 实现原理
1. **日期处理**:使用 `date-fns` 库进行日期计算和格式化
2. **日历生成**:
- 计算每个月的第一天和最后一天
- 生成包含前置天数(上个月的最后几天)的完整日历网格
- 为每个日期添加附加信息(是否当前月、是否今天、是否周末)
3. **事件模拟**:随机生成事件数据用于演示
4. **响应式数据**:使用 Vue 的 computed 属性确保日期数据动态更新
5. **导航功能**:通过 `addMonths` 和 `subMonths` 实现月份切换
阅读全文