You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="calendar-widget">
<!-- 月份切换加载指示器 -->
<!-- <view v-if="switchingMonth" class="month-loading">
<u-loading mode="circle" size="24"></u-loading>
<text class="loading-text">加载中...</text>
</view> -->
<!-- 自定义日历(支持跨天横条显示) -->
<view class="calendar-title">学院日历</view>
<view class="calendar-container" :class="{ 'loading-opacity': switchingMonth }">
<CalendarGrid
:month="calendarDate"
:events="courses"
:rowHeightRpx="146"
:headerHeightRpx="60"
:weekHeaderHeightRpx="40"
:dateNumberHeightRpx="36"
@dayClick="onDateChange"
@monthChange="onMonthSwitch"
@edit="onEditEvent"
@eventClick="showCourseDetail"
/>
</view>
</view>
</template>
<script>
import CalendarGrid from '@/components/calendar-grid/calendar-grid.vue'
export default {
name: 'CalendarWidget',
components: {
CalendarGrid
},
data() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return {
calendarDate: `${year}-${month}`,
courses: [],
monthEvents: [],
loading: false,
switchingMonth: false
}
},
mounted() {
this.loadCourses()
},
methods: {
// 解析日期时间字符串
parseDateTime(dateTimeStr) {
if (!dateTimeStr) return null
const [datePart, timePart = '00:00:00'] = dateTimeStr.trim().split(/[T\s]+/)
const [y, m, d] = datePart.split('-').map(n => parseInt(n, 10))
const [hh = 0, mm = 0, ss = 0] = timePart.split(':').map(n => parseInt(n, 10))
return new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0, ss || 0)
},
// 加载课程数据(初始加载)
async loadCourses() {
if (this.loading) return
try {
this.loading = true
await this.loadCoursesForMonth(this.calendarDate)
} catch (error) {
console.error('加载课程数据失败:', error)
uni.showToast({
title: '加载日历数据失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 加载指定月份的课程数据
async loadCoursesForMonth(monthDate) {
const res = await this.$u.api.calendarsGet({
month: monthDate
})
const rows = (res && res.data) ? res.data : (Array.isArray(res) ? res : [])
this.courses = Array.isArray(rows) ? rows : []
console.log('日历数据加载成功:', this.courses)
},
// 日期变化
onDateChange({ fulldate }) {
const evs = this.getEventsForDate(fulldate)
if (evs.length) {
// 简单按开始时间排序
evs.sort((a,b) => this.parseDateTime(a.start_time) - this.parseDateTime(b.start_time))
this.showCourseDetail(evs[0])
}
},
// 月份切换
async onMonthSwitch({ year, month }) {
const newDate = `${year}-${String(month).padStart(2, '0')}`
// 如果已经是当前月份,不需要重新加载
if (newDate === this.calendarDate) return
// 设置切换状态
this.switchingMonth = true
try {
// 更新日历日期
this.calendarDate = newDate
// 加载新月份的数据
await this.loadCoursesForMonth(newDate)
} catch (error) {
console.error('月份切换失败:', error)
uni.showToast({
title: '切换月份失败',
icon: 'none'
})
} finally {
// 延迟一点时间让用户看到切换效果
setTimeout(() => {
this.switchingMonth = false
}, 300)
}
},
// 编辑事件
onEditEvent(event) {
console.log('编辑事件:', event)
},
// 获取指定日期的事件
getEventsForDate(dateStr) {
const targetDate = new Date(dateStr)
targetDate.setHours(0, 0, 0, 0)
return this.courses.filter(ev => {
const startDate = this.parseDateTime(ev.start_time)
const endDate = ev.end_time ? this.parseDateTime(ev.end_time) : this.parseDateTime(ev.start_time)
if (!startDate || !endDate) return false
startDate.setHours(0,0,0,0)
endDate.setHours(0,0,0,0)
return targetDate >= startDate && targetDate <= endDate
})
},
// 显示课程详情
showCourseDetail(ev) {
// 跳转逻辑:
// type=1 课程:直接跳转课程详情页面
// type=3 自定义事件:弹出详情
// type=4 资讯:跳 webview
const type = ev.type
if (type === 1) {
if (ev.course_id) {
uni.navigateTo({ url: `/packages/course/detail?id=${ev.course_id}` })
return
}
// 没有 course_id 则显示基本信息
uni.showModal({
title: ev.title || '课程详情',
content: `时间:${ev.start_time}\n地点${ev.location || '待定'}`,
showCancel: false
})
return
}
if (type === 3) {
uni.showModal({
title: ev.title || '事件详情',
content: ev.content || '暂无详细信息',
showCancel: false
})
return
}
if (type === 4) {
if (ev.url) {
const encoded = ev.url
uni.navigateTo({ url: `/packages/webview/index?type=3&url=${encoded}` })
return
}
// 没有 url 则显示基本信息
uni.showModal({
title: ev.title || '资讯详情',
content: ev.content || '暂无详细信息',
showCancel: false
})
return
}
// 其他类型默认显示基本信息
uni.showModal({
title: ev.title || '详情',
content: ev.content || '暂无详细信息',
showCancel: false
})
}
}
}
</script>
<style scoped>
.calendar-widget {
padding: 25rpx;
}
.month-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 12rpx 20rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 20rpx;
margin-bottom: 12rpx;
position: relative;
z-index: 10;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.loading-text {
margin-left: 12rpx;
font-size: 22rpx;
color: #666;
}
.loading-opacity {
opacity: 0.7;
transition: opacity 0.2s ease;
pointer-events: none;
}
.calendar-container {
margin-bottom: 0;
}
.calendar-title{
font-size: 34rpx;
color: #fff;
border-radius: 20rpx 20rpx 0 0;
text-align: center;
background:linear-gradient(90deg, #ddba99, #b18d6d);
padding:20rpx;
}
</style>