第一次接触Element UI的el-calendar组件时,我就被它的灵活性惊艳到了。相比其他日历组件,el-calendar最大的优势在于它提供了完整的日期单元格自定义能力。这意味着我们可以在每个日期格子里自由添加任务状态、进度条、甚至是迷你图表。
实际开发中,我遇到过不少团队都在用表格或列表管理任务,但这种方式有个致命问题——无法直观看到任务在时间线上的分布。而el-calendar正好解决了这个痛点。比如上周有个产品经理跑来问我:"能不能一眼看出下周哪天任务最密集?"用el-calendar配合简单的计数显示,这个问题就迎刃而解了。
首先确保你的Vue项目已经安装了Element UI。如果还没装,执行:
bash复制npm install element-ui -S
然后在main.js中全局引入:
javascript复制import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
创建一个Calendar.vue组件,先实现最基础的日历展示:
vue复制<template>
<div class="calendar-container">
<el-calendar v-model="currentDate">
<!-- 这里会放我们的自定义内容 -->
</el-calendar>
</div>
</template>
<script>
export default {
data() {
return {
currentDate: new Date()
}
}
}
</script>
<style scoped>
.calendar-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
</style>
这时候页面上应该已经显示出一个标准的月历视图了。但你会发现每个日期格子都是空白的——这正是我们需要自定义的地方。
el-calendar提供了dateCell这个具名插槽,配合slot-scope可以获取到当前单元格的数据。这个data对象包含几个关键属性:
改造我们的模板:
vue复制<el-calendar v-model="currentDate">
<template #dateCell="{data}">
<div class="date-cell">
<div class="day-number">{{ data.day.split('-')[2] }}</div>
<!-- 这里会放任务标记 -->
</div>
</template>
</el-calendar>
假设我们从后端获取的任务数据格式如下:
javascript复制tasks: [
{
date: '2023-07-15',
completed: 3,
total: 5,
notes: ['会议纪要', '需求评审']
}
]
我们可以这样渲染任务状态:
vue复制<template #dateCell="{data}">
<div class="date-cell" @click="handleDateClick(data)">
<div class="day-number">{{ data.day.split('-')[2] }}</div>
<div v-if="getTasksForDate(data.day)" class="task-indicator">
<el-progress
:percentage="calculateCompletion(data.day)"
:show-text="false"
:stroke-width="8"
/>
<span class="task-count">
{{ getTasksForDate(data.day).completed }}/{{ getTasksForDate(data.day).total }}
</span>
</div>
</div>
</template>
对应的methods:
javascript复制methods: {
getTasksForDate(date) {
return this.tasks.find(task => task.date === date)
},
calculateCompletion(date) {
const task = this.getTasksForDate(date)
return task ? Math.round((task.completed / task.total) * 100) : 0
}
}
注意到上面的模板中我们已经添加了@click事件。现在实现handleDateClick方法:
javascript复制handleDateClick(cellData) {
this.selectedDate = cellData.day
this.showTaskDialog = true
this.currentTasks = this.getTasksForDate(cellData.day) || {
date: cellData.day,
completed: 0,
total: 0,
notes: []
}
}
使用el-dialog展示任务详情:
vue复制<el-dialog
:title="`${selectedDate} 任务详情`"
:visible.sync="showTaskDialog"
width="50%"
>
<div v-if="currentTasks.notes.length">
<h4>任务列表</h4>
<ul class="task-list">
<li v-for="(note, index) in currentTasks.notes" :key="index">
<el-checkbox v-model="currentTasks.completed" @change="updateCompletion">
{{ note }}
</el-checkbox>
</li>
</ul>
</div>
<div v-else>
<p>当天没有安排任务</p>
</div>
<el-input
v-model="newTask"
placeholder="输入新任务"
class="new-task-input"
/>
<span slot="footer">
<el-button @click="showTaskDialog = false">取消</el-button>
<el-button type="primary" @click="addTask">添加任务</el-button>
</span>
</el-dialog>
创建api.js处理所有日历相关的请求:
javascript复制import axios from 'axios'
const api = axios.create({
baseURL: 'https://your-api-endpoint.com/api'
})
export default {
getCalendarTasks(month) {
return api.get('/tasks', {
params: { month }
})
},
updateTask(task) {
return task.id
? api.put(`/tasks/${task.id}`, task)
: api.post('/tasks', task)
}
}
在组件中调用:
javascript复制async fetchTasks() {
const month = this.currentDate.toISOString().slice(0,7)
try {
const res = await api.getCalendarTasks(month)
this.tasks = res.data
} catch (error) {
this.$message.error('获取任务数据失败')
}
}
建议采用两种数据同步方式:
javascript复制mounted() {
this.fetchTasks()
}
javascript复制watch: {
currentDate(newVal) {
this.fetchTasks()
}
}
在style部分添加这些样式可以让日历更美观:
css复制.date-cell {
height: 100%;
padding: 5px;
position: relative;
}
.day-number {
font-weight: bold;
margin-bottom: 3px;
}
.task-indicator {
position: absolute;
bottom: 5px;
left: 0;
right: 0;
padding: 0 5px;
}
.task-count {
font-size: 10px;
display: block;
text-align: center;
margin-top: 2px;
}
/deep/ .el-calendar-table .el-calendar-day {
height: 80px !important;
padding: 0 !important;
}
当任务数据量很大时,可以采取以下优化措施:
后端返回的日期字符串可能包含时区信息,建议在前端统一处理:
javascript复制function normalizeDate(dateStr) {
return new Date(dateStr).toISOString().split('T')[0]
}
针对移动设备需要调整日历显示:
css复制@media (max-width: 768px) {
/deep/ .el-calendar-table .el-calendar-day {
height: 60px !important;
}
.task-indicator {
display: none;
}
}
在实现过程中,我发现el-calendar的响应式表现已经很不错,但密集的任务标记在小屏幕上会显得拥挤。这时候可以考虑在移动端只显示完成比例,点击后才展示详情。