<template>
<div class="schedule-management">
<!-- 日历区域 -->
<div id="calendar-container" class="calendar-container">
<!-- 打印按钮 -->
<div class="calendar-toolbar">
<el-button type="primary" size="small" @click="openPrintPreview">打印预览</el-button>
</div>
<el-calendar v-model="selectedDate" @select="handleCalendar-select">
<template #date-cell="{ data }">
<div class="calendar-date-cell" @click="handleDateCellClick(data.day)">
<span class="date-number">{{ data.day.split('-')[2] }}</span>
<!-- 展示当天的日程列表 -->
<div class="schedule-list-on-date">
<div
v-for="schedule in getDaySchedules(data.day)"
:key="schedule.id"
class="schedule-item-on-date"
:class="`schedule-item-on-date priority-${schedule.priority}`"
@click.stop="viewScheduleDetails(schedule)"
>
<span class="schedule-title">{{ schedule.title }}</span>
</div>
<!-- 如果日程太多,显示省略号 -->
<div
v-if="getDaySchedules(data.day).length > maxSchedulesToShow"
class="more-schedules"
>
+{{ getDaySchedules(data.day).length - maxSchedulesToShow }} 更多
</div>
</div>
</div>
</template>
</el-calendar>
</div>
<!-- 日程详情侧边栏 -->
<div class="schedule-sidebar" v-if="selectedSchedule">
<div class="sidebar-header">
<h3>{{ formatDateTitle(selectedDate) }} 日程详情</h3>
<el-button type="text" @click="selectedSchedule = null" icon="Close">关闭</el-button>
</div>
<div class="schedule-detail">
<h4>{{ selectedSchedule.title }}</h4>
<p><strong>时间:</strong> {{ selectedSchedule.startTime }} - {{ selectedSchedule.endTime }}</p>
<p><strong>优先级:</strong>
<el-tag
:type="selectedSchedule.priority === 'high' ? 'danger' :
selectedSchedule.priority === 'medium' ? 'warning' : 'success'"
size="small"
>
{{ selectedSchedule.priority === 'high' ? '高' :
selectedSchedule.priority === 'medium' ? '中' : '低' }}
</el-tag>
</p>
<p v-if="selectedSchedule.description"><strong>描述:</strong> {{ selectedSchedule.description }}</p>
<div class="detail-actions">
<el-button size="small" @click="editSchedule(selectedSchedule)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteSchedule(selectedSchedule.id)">删除</el-button>
</div>
</div>
</div>
<!-- 添加/编辑日程对话框 -->
<el-dialog
v-model="dialogVisible"
:title="isEditing ? '编辑日程' : '添加日程'"
width="500px"
:before-close="handleDialogClose"
>
<el-form :model="scheduleForm" :rules="formRules" ref="formRef" label-width="80px">
<el-form-item label="标题" prop="title">
<el-input v-model="scheduleForm.title" placeholder="请输入日程标题" />
</el-form-item>
<el-form-item label="日期" prop="date">
<el-date-picker
v-model="scheduleForm.date"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="时间" prop="time">
<el-time-picker
v-model="scheduleForm.startTime"
placeholder="开始时间"
format="HH:mm"
value-format="HH:mm"
style="width: 48%"
/>
<span style="margin: 0 4px">-</span>
<el-time-picker
v-model="scheduleForm.endTime"
placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
style="width: 48%"
/>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-select v-model="scheduleForm.priority" placeholder="选择优先级" style="width: 100%">
<el-option label="低" value="low"></el-option>
<el-option label="中" value="medium"></el-option>
<el-option label="高" value="high"></el-option>
</el-select>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="scheduleForm.description"
type="textarea"
:rows="3"
placeholder="请输入日程描述"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveSchedule">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { add, del, update, list } from '@/api/calendar'
import print from "print-js";
// 响应式数据
const selectedDate = ref(new Date())
const dialogVisible = ref(false)
const isEditing = ref(false)
const formRef = ref()
const selectedSchedule = ref(null)
const maxSchedulesToShow = ref(3)
// 日程数据
const schedules = ref([])
// 表单数据
const scheduleForm = ref({
id: null,
title: '',
date: '',
startTime: '',
endTime: '',
priority: 'medium',
description: ''
})
// 表单验证规则
const formRules = {
title: [{ required: true, message: '请输入日程标题', trigger: 'blur' }],
date: [{ required: true, message: '请选择日期', trigger: 'change' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
}
// 获取指定日期的日程(最多显示 maxSchedulesToShow 条)
const getDaySchedules = (date) => {
return schedules.value.filter(schedule => schedule.date === date).slice(0, maxSchedulesToShow.value)
}
// 格式化日期标题
const formatDateTitle = (date) => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}年${month}月${day}日`
}
// 日历选择事件
const handleCalendarSelect = (date) => {
selectedDate.value = new Date(date)
}
// 点击日期单元格
const handleDateCellClick = (dateString) => {
isEditing.value = false
scheduleForm.value = {
id: null,
title: '',
date: dateString,
startTime: '09:00',
endTime: '10:00',
priority: 'medium',
description: ''
}
dialogVisible.value = true
}
// 查看日程详情
const viewScheduleDetails = (schedule) => {
selectedSchedule.value = schedule
}
// 编辑日程
const editSchedule = (schedule) => {
isEditing.value = true
scheduleForm.value = { ...schedule }
dialogVisible.value = true
}
// 删除日程
const deleteSchedule = async (id) => {
try {
await ElMessageBox.confirm('确认删除该日程吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
del(id).then(() => {
ElMessage.success('删除成功')
selectedSchedule.value = null
refreshCalendar()
})
} catch {
// 取消操作
}
}
// 保存日程
const saveSchedule = () => {
formRef.value?.validate((valid) => {
if (valid) {
if (isEditing.value) {
update(scheduleForm.value).then(() => {
refreshCalendar()
ElMessage.success('日程更新成功')
dialogVisible.value = false
})
} else {
add({
title: scheduleForm.value.title,
date: scheduleForm.value.date,
startTime: scheduleForm.value.startTime,
endTime: scheduleForm.value.endTime,
priority: scheduleForm.value.priority,
description: scheduleForm.value.description
}).then(() => {
ElMessage.success('日程添加成功')
refreshCalendar()
dialogVisible.value = false
})
}
}
})
}
// 关闭对话框
const handleDialogClose = (done) => {
formRef.value?.clearValidate()
done()
}
// 刷新日历数据
function refreshCalendar(year, month) {
const y = year ?? selectedDate.value.getFullYear()
const m = month ?? selectedDate.value.getMonth() + 1
const strStartDate = `${y}-${String(m).padStart(2, '0')}-01`
const lastDay = new Date(y, m, 0).getDate()
const strEndDate = `${y}-${String(m).padStart(2, '0')}-${lastDay}`
list({ from: strStartDate, to: strEndDate })
.then(res => {
if (res.data?.result) {
schedules.value = res.data.result
}
})
.catch(err => {
console.error('加载日程失败:', err)
ElMessage.error('加载日程失败')
})
}
// 打印预览
const openPrintPreview = () => {
printJS({
printable: 'calendar-container', // 给日历容器加 id="calendar-container"
type: 'html',
targetStyles: ['*'],
documentTitle: '日程表 - ' + formatDateTitle(selectedDate.value),
// 可选:添加额外打印样式
style: `
.el-calendar__header { display: none !important; }
.calendar-toolbar { display: none !important; }
`
})
}
// 监听月份变化
let lastMonthKey = ''
watch(selectedDate, (newDate) => {
const key = `${newDate.getFullYear()}-${newDate.getMonth()}`
if (key !== lastMonthKey) {
lastMonthKey = key
refreshCalendar(newDate.getFullYear(), newDate.getMonth() + 1)
}
})
// 初始化
onMounted(() => {
refreshCalendar(selectedDate.value.getFullYear(), selectedDate.value.getMonth() + 1)
})
</script>
<style scoped>
.schedule-management {
padding: 10px;
background-color: #f5f7fa;
min-height: calc(100vh - 260px);
display: flex;
gap: 20px;
}
.calendar-container {
flex: 1;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
:deep(.el-calendar) {
width: 100%;
}
:deep(.el-calendar__header) {
padding: 15px 20px;
}
:deep(.el-calendar-day) {
height: 120px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: 5px;
position: relative;
}
.calendar-date-cell {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
cursor: pointer;
}
.date-number {
font-weight: bold;
font-size: 14px;
margin-bottom: 4px;
align-self: flex-end;
}
.schedule-list-on-date {
flex: 1;
width: 100%;
overflow-y: auto;
padding-right: 2px;
}
.schedule-item-on-date {
display: flex;
align-items: center;
padding: 2px 4px;
margin-bottom: 2px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.schedule-item-on-date:hover {
background-color: #f0f9ff;
}
.schedule-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.more-schedules {
font-size: 10px;
color: #909399;
padding: 2px 4px;
cursor: pointer;
}
.schedule-sidebar {
width: 350px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 20px;
height: fit-content;
max-height: calc(100vh - 120px);
overflow-y: auto;
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
}
.schedule-detail h4 {
margin: 0 0 15px 0;
color: #303133;
}
.schedule-detail p {
margin: 8px 0;
color: #606266;
line-height: 1.5;
}
.detail-actions {
margin-top: 20px;
display: flex;
gap: 10px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* 优先级样式 */
.schedule-item-on-date.priority-high {
background-color: #fef0f0;
color: #e54d42;
border-left: 2px solid #f56c6c;
font-weight: 500;
}
.schedule-item-on-date.priority-medium {
background-color: #fdf6ec;
color: #e6a23c;
border-left: 2px solid #e6a23c;
font-weight: 500;
}
.schedule-item-on-date.priority-low {
background-color: #f0f9ff;
color: #409eff;
border-left: 2px solid #409eff;
font-weight: 500;
}
/* 打印专用样式 */
@media print {
/* 隐藏所有元素 */
body * {
visibility: hidden;
}
/* 仅显示日历容器及其内容 */
.calendar-container,
.calendar-container * {
visibility: visible;
}
/* 定位到顶部,占满页面 */
.calendar-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
margin: 0;
padding: 0;
background: white !important;
box-shadow: none;
border: none;
}
/* 隐藏日历头部(月份切换区域) */
:deep(.el-calendar__header) {
display: none !important;
}
/* 隐藏工具栏(打印按钮) */
.calendar-toolbar {
display: none !important;
}
/* 隐藏侧边栏、弹窗等 */
.schedule-sidebar,
.el-dialog__wrapper,
.el-message,
.el-message-box__wrapper {
display: none !important;
}
/* 隐藏所有按钮 */
button, .el-button {
display: none !important;
}
/* 打印优化:字体、间距、颜色 */
:deep(.el-calendar-day) {
height: 80px !important;
padding: 4px !important;
font-size: 10px !important;
border: 1px solid #eee !important;
box-sizing: border-box;
}
.date-number {
font-size: 12px !important;
font-weight: bold;
color: black !important;
margin-bottom: 2px;
}
.schedule-item-on-date {
font-size: 9px !important;
padding: 1px 2px !important;
margin-bottom: 1px !important;
white-space: normal !important;
line-height: 1.2 !important;
overflow: hidden;
text-overflow: ellipsis;
background: #f9f9f9 !important;
border-left: 2px solid #999 !important;
color: black !important;
font-weight: normal !important;
}
.more-schedules {
font-size: 8px !important;
color: #666 !important;
}
}
</style>