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.

601 lines
17 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-page">
<!-- 顶部导航 -->
<!-- <view class="header">
<view class="logo-section">
<view class="logo"></view>
<view class="school-name">苏州科技商学院</view>
</view>
<view class="user-section">
<text>张同学</text>
<view class="vip-badge">VIP会员</view>
</view>
</view> -->
<!-- 课程类型筛选 -->
<view class="calendar-filters">
<view
v-for="item in filterTabs"
:key="item.value"
:class="['filter-badge', {active: filterType === item.value}]"
@tap="onFilterChange(item.value)"
>
{{ item.label }}
</view>
</view>
<!-- 日历 -->
<view class="calendar-container">
<uni-calendar
:insert="true"
:lunar="true"
:selected="selectedDates"
@change="onDateChange"
@monthSwitch="onMonthSwitch"
/>
</view>
<!-- 当月日程列表 -->
<view class="month-events-container">
<view class="events-title">当月日程</view>
<scroll-view v-if="monthEvents.length" class="events-list" scroll-y>
<view
v-for="ev in monthEvents"
:key="ev.id"
class="event-item"
@tap="showCourseDetail(ev)"
>
<view class="event-item-left">
<view :class="['type-dot', 'event-' + ev.type]"></view>
<view class="event-info">
<view class="event-title">{{ ev.title }}</view>
<view class="event-meta">
<text>{{ getCourseTypeName(ev.type) }}</text>
<text> | {{ formatDateTimeRange(ev.start, ev.end) }}</text>
</view>
</view>
</view>
<view class="event-item-right">
<u-icon name="arrow-right" color="#999" size="28"></u-icon>
</view>
</view>
</scroll-view>
<view v-else class="empty-events">
<text>本月暂无日程</text>
</view>
</view>
<!-- 课程详情弹窗 -->
<u-popup v-model="showDetail" :width="'100vw'" :height="'100vh'" mode="center" :closeable="true" :mask-close-able="true">
<view class="course-modal">
<view class="modal-header">
<text class="modal-title">{{ detailData.title }}</text>
</view>
<view class="modal-body">
<view class="course-info">
<view class="info-item">
<text class="info-label">课程类型</text>
<text class="info-value">{{ getCourseTypeName(detailData.type) }}</text>
</view>
<view class="info-item">
<text class="info-label">开课时间</text>
<text class="info-value">{{ formatDateTime(detailData.start) }}</text>
</view>
<view class="info-item">
<text class="info-label">课程地点</text>
<text class="info-value">{{ detailData.location }}</text>
</view>
<view class="info-item">
<text class="info-label">主讲老师</text>
<text class="info-value">{{ detailData.teacher }}</text>
</view>
</view>
<view class="course-description">
<text class="desc-title">课程简介</text>
<text class="desc-content">{{ detailData.description }}</text>
</view>
<view v-if="detailData.vipContent" class="course-description vip-content">
<text class="desc-title">VIP专享内容</text>
<view class="desc-content">
<text>课程大纲:</text>
<view>模块一:人工智能基础理论</view>
<view>模块二:具身智能技术应用</view>
<view>模块三:产业落地实践</view>
<text>课程资料:</text>
<view>课程讲义下载</view>
<view>案例分析材料</view>
<view>实践项目指导</view>
</view>
</view>
<view class="course-actions">
<!-- <u-button type="default" @tap="showDetail = false">关闭</u-button> -->
<!-- <u-button type="primary" @tap="enrollCourse"></u-button> -->
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import uniCalendar from '@/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue'
export default {
components:{
uniCalendar
},
data() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return {
filterTabs: [
{ label: '全部', value: 'all' },
{ label: '课程', value: 'course' },
{ label: '日程', value: 'activity' },
{ label: '资讯', value: 'workshop' }
],
filterType: 'all',
calendarDate: `${year}-${month}`,
showDetail: false,
detailData: {},
courses: [
{
id: 1,
title: '2025产业加速营 | 智能制造专题',
type: 'course',
start: '2025-07-04T09:00:00',
end: '2025-07-04T17:00:00',
location: '商学院A101',
teacher: '王教授',
description: '聚焦智能制造领域的最新发展与应用,邀请行业专家深度解析。',
vipContent: true,
enrollmentDeadline: '2025-06-01'
},
{
id: 2,
title: '校友企业参访 | 新能源企业',
type: 'activity',
start: '2025-07-10T13:30:00',
end: '2025-07-10T17:00:00',
location: '苏州高新区',
teacher: '李总监',
description: '走进新能源龙头企业,了解绿色科技创新。',
vipContent: false,
enrollmentDeadline: '2025-06-07'
},
{
id: 3,
title: '移动课堂 | AI商业落地',
type: 'workshop',
start: '2025-07-15T09:00:00',
end: '2025-07-15T16:00:00',
location: '创新实验室B202',
teacher: '张教授',
description: '实战演练AI项目商业化流程提升创业能力。',
vipContent: true,
enrollmentDeadline: '2025-06-12'
},
{
id: 4,
title: '2025校友论坛 | 数字经济新机遇',
type: 'activity',
start: '2025-07-20T14:00:00',
end: '2025-07-20T17:30:00',
location: '报告厅',
teacher: '特邀嘉宾',
description: '聚焦数字经济发展趋势,促进校友交流合作。',
vipContent: false,
enrollmentDeadline: '2025-06-17'
},
{
id: 5,
title: '2025暑期创新营 | 项目路演',
type: 'course',
start: '2025-07-27T09:00:00',
end: '2025-07-27T12:00:00',
location: '多功能厅',
teacher: '创业导师团',
description: '学员项目成果展示与专家点评。',
vipContent: true,
enrollmentDeadline: '2025-06-24'
},
{
id: 6,
title: '2025创新创业移动课堂跨天',
type: 'workshop',
start: '2025-06-08T09:00:00',
end: '2025-06-10T17:00:00',
location: '创新基地C301',
teacher: '创业导师团',
description: '三天两夜创新创业实战训练,团队协作与路演。',
vipContent: true,
enrollmentDeadline: '2025-06-05'
},
{
id: 7,
title: '2025校友企业家沙龙',
type: 'activity',
start: '2025-06-13T18:00:00',
end: '2025-06-13T21:00:00',
location: '校友会馆',
teacher: '企业家代表',
description: '校友企业家分享创业经验与行业洞察。',
vipContent: false,
enrollmentDeadline: '2025-06-10'
},
{
id: 8,
title: '2025人工智能专题课程跨天',
type: 'course',
start: '2025-06-17T09:00:00',
end: '2025-06-19T17:00:00',
location: '商学院A201',
teacher: 'AI专家组',
description: '为期三天的AI理论与实操课程含项目实践。',
vipContent: true,
enrollmentDeadline: '2025-06-14'
},
{
id: 9,
title: '2025校友羽毛球友谊赛',
type: 'activity',
start: '2025-06-23T14:00:00',
end: '2025-06-23T18:00:00',
location: '体育馆',
teacher: '体育部',
description: '校友间的体育交流与友谊赛。',
vipContent: false,
enrollmentDeadline: '2025-06-20'
},
{
id: 10,
title: '2025创新项目孵化移动课堂跨天',
type: 'workshop',
start: '2025-06-25T09:00:00',
end: '2025-06-28T17:00:00',
location: '孵化基地',
teacher: '孵化导师团',
description: '连续四天的创新项目孵化与辅导,含结营路演。',
vipContent: true,
enrollmentDeadline: '2025-06-22'
}
]
}
},
computed: {
selectedDates() {
const dates = new Set();
this.courses.forEach(ev => {
if (this.filterType === 'all' || ev.type === this.filterType) {
const start = new Date(ev.start);
const end = new Date(ev.end);
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
dates.add(d.toISOString().slice(0, 10));
}
}
});
return Array.from(dates).map(date => ({ date, info: '' }));
},
monthEvents() {
if (!this.calendarDate) return [];
const [year, month] = this.calendarDate.split('-').map(Number);
const filtered = this.courses.filter(ev => {
if (this.filterType !== 'all' && ev.type !== this.filterType) return false;
const evDate = new Date(ev.start);
return evDate.getFullYear() === year && (evDate.getMonth() + 1) === month;
});
return filtered.sort((a, b) => new Date(a.start) - new Date(b.start));
}
},
methods: {
onFilterChange(type) {
this.filterType = type;
},
onDateChange({ fulldate }) {
const evs = this.getEventsForDate(fulldate);
if (evs.length) {
// 如果当天有多个事件,优先显示课程,然后是移动课堂,最后是日程
evs.sort((a,b) => {
const order = { course: 1, workshop: 2, activity: 3 };
return (order[a.type] || 9) - (order[b.type] || 9);
});
this.showCourseDetail(evs[0]);
}
},
onMonthSwitch({ year, month }) {
this.calendarDate = `${year}-${String(month).padStart(2, '0')}`;
},
getEventsForDate(dateStr) {
// 返回当天所有事件
const targetDate = new Date(dateStr);
targetDate.setHours(0, 0, 0, 0);
return this.courses.filter(ev => {
if (this.filterType !== 'all' && ev.type !== this.filterType) return false;
const startDate = new Date(ev.start);
startDate.setHours(0, 0, 0, 0);
const endDate = new Date(ev.end);
endDate.setHours(0, 0, 0, 0);
return targetDate >= startDate && targetDate <= endDate;
});
},
showCourseDetail(ev) {
this.detailData = ev;
this.showDetail = true;
this.checkEnrollmentDeadline(ev);
},
enrollCourse() {
uni.showModal({
title: '提示',
content: `确定要报名"${this.detailData.title}"吗?`,
success: res => {
if (res.confirm) {
uni.showToast({ title: '报名成功', icon: 'success' });
this.showDetail = false;
}
}
});
},
checkEnrollmentDeadline(course) {
const deadline = new Date(course.enrollmentDeadline);
const now = new Date();
const daysUntilDeadline = Math.ceil((deadline - now) / (1000 * 60 * 60 * 24));
if (daysUntilDeadline <= 3 && daysUntilDeadline > 0) {
// 可用uni.showToast或其他提醒
uni.showToast({
title: `${course.title}报名即将截止`,
icon: 'none'
});
}
},
getCourseTypeName(type) {
const types = {
course: '课程',
activity: '日程',
workshop: '资讯'
};
return types[type] || type;
},
formatDateTime(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
},
formatDateTimeRange(startStr, endStr) {
const start = new Date(startStr);
const end = new Date(endStr);
const startDay = `${String(start.getMonth() + 1).padStart(2,'0')}-${String(start.getDate()).padStart(2,'0')}`;
const startTime = `${String(start.getHours()).padStart(2,'0')}:${String(start.getMinutes()).padStart(2,'0')}`;
const endDay = `${String(end.getMonth() + 1).padStart(2,'0')}-${String(end.getDate()).padStart(2,'0')}`;
const endTime = `${String(end.getHours()).padStart(2,'0')}:${String(end.getMinutes()).padStart(2,'0')}`;
if (startDay === endDay) {
return `${startDay} ${startTime} - ${endTime}`;
} else {
return `${startDay} ${startTime} - ${endDay} ${endTime}`;
}
}
}
}
</script>
<style scoped>
.calendar-page {
min-height: 100vh;
background: #f8f9fa;
padding-bottom: 40rpx;
}
.header {
background: #fff;
padding: 20rpx 30rpx 10rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
position: sticky;
top: 0;
z-index: 100;
}
.logo-section {
display: flex;
align-items: center;
gap: 15rpx;
}
.logo {
width: 40rpx;
height: 40rpx;
background: #3498db;
border-radius: 8rpx;
}
.school-name {
font-size: 32rpx;
font-weight: 600;
color: #2c3e50;
margin-left: 10rpx;
}
.user-section {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 28rpx;
}
.vip-badge {
background: linear-gradient(45deg, #FFD700, #FFA500);
color: white;
padding: 4rpx 12rpx;
border-radius: 4rpx;
font-size: 22rpx;
font-weight: 500;
margin-left: 8rpx;
}
.calendar-filters {
display: flex;
gap: 18rpx;
align-items: center;
padding: 18rpx 20rpx 0 20rpx;
margin-bottom:20rpx;
}
.filter-badge {
padding: 8rpx 22rpx;
border-radius: 20rpx;
font-size: 26rpx;
font-weight: 500;
background: #f8f9fa;
color: #2c3e50;
transition: all 0.3s;
}
.filter-badge.active {
background: #3498db;
color: #fff;
}
.calendar-container {
background: #fff;
border-radius: 18rpx;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin: 20rpx;
padding: 10rpx 0 20rpx 0;
}
.month-events-container {
margin: 20rpx;
padding: 20rpx;
background: #fff;
border-radius: 18rpx;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.events-title {
font-size: 32rpx;
font-weight: 600;
color: #2c3e50;
margin-bottom: 20rpx;
}
.events-list {
max-height: 400rpx;
}
.event-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.event-item:last-child {
border-bottom: none;
}
.event-item-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.type-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
flex-shrink: 0;
}
.event-course { background: #1565c0; }
.event-activity { background: #6a1b9a; }
.event-workshop { background: #2e7d32; }
.event-info {
display: flex;
flex-direction: column;
}
.event-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
}
.event-meta {
font-size: 24rpx;
color: #888;
}
.empty-events {
text-align: center;
color: #999;
padding: 40rpx 0;
font-size: 28rpx;
}
.course-modal {
background: #fff;
border-radius: 18rpx;
padding: 20rpx 20rpx 10rpx 20rpx;
width: 100vw;
height:100vh;
overflow: scroll;
}
.modal-header {
border-bottom: 1rpx solid #e9ecef;
padding-bottom: 10rpx;
margin-bottom: 10rpx;
}
.modal-title {
font-size: 34rpx;
font-weight: 700;
color: #1565c0;
}
.modal-body {
padding: 0;
}
.course-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10rpx;
margin-bottom: 18rpx;
}
.info-item {
background: #f8f9fa;
padding: 10rpx;
border-radius: 8rpx;
}
.info-label {
font-size: 28rpx;
color: #6c757d;
}
.info-value {
font-size: 28rpx;
font-weight: 500;
color: #2c3e50;
}
.course-description {
background: #f8f9fa;
padding: 16rpx;
border-radius: 8rpx;
margin-bottom: 12rpx;
}
.desc-title {
color: #1565c0;
font-size: 28rpx;
font-weight: 600;
margin-bottom: 6rpx;
display: block;
}
.desc-content {
color: #333;
font-size: 28rpx;
margin-top: 4rpx;
}
.vip-content {
/* background: linear-gradient(90deg, #fffbe6 0%, #fff3cd 100%); */
/* border: 1rpx solid #ffe082; */
}
.course-actions {
display: flex;
gap: 18rpx;
justify-content: flex-end;
margin-top: 10rpx;
}
@media (max-width: 768px) {
.course-modal { min-width: 90vw; }
.calendar-container { margin: 4rpx; }
.header { padding: 8rpx 4rpx; }
.school-name { font-size: 28rpx; }
}
</style>