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.

377 lines
12 KiB

<template>
<div class="admin-calendar">
<div class="admin-header">
<i class="el-icon-date"></i> 课程日历管理
</div>
<div class="admin-main">
<!-- 数据拉取区 -->
<div class="admin-panel" style="max-width: 350px;">
<div class="admin-panel-title"><i class="el-icon-download"></i> 数据拉取区</div>
<el-form label-width="90px" size="small">
<el-form-item label="数据类型">
<el-select v-model="dataType" @change="onTypeChange" placeholder="请选择类型">
<el-option label="课程" value="course" />
<el-option label="活动" value="activity" />
<el-option label="移动课堂" value="workshop" />
</el-select>
</el-form-item>
<el-form-item label="已有数据">
<el-select v-model="dataId" @change="onDataChange" placeholder="请选择数据">
<el-option v-for="item in dataList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
<div class="text-secondary" style="font-size:13px;min-height:20px;">{{ dataDateInfo }}</div>
<el-button type="primary" icon="el-icon-link" style="width:100%;margin-top:10px;"
@click="fetchData">拉取并关联</el-button>
</el-form>
<el-button type="success" icon="el-icon-plus" style="width:100%;margin-top:10px;"
@click="openCreateModal('add')">新建日历事件</el-button>
</div>
<!-- 日历预览区 -->
<div class="admin-panel" style="min-width:0;flex:2;">
<div class="admin-panel-title"><i class="el-icon-view"></i> 日历预览区</div>
<el-calendar v-model="calendarDate">
<template slot="dateCell" slot-scope="{date}">
<div class="cell-content">
<span>{{ date.getDate() }}</span>
<div v-for="ev in eventsForDate(date)" :key="ev._id" class="event-dot" :title="ev.title"
@click.stop="showEvent(ev)"></div>
</div>
</template>
</el-calendar>
<div class="mt-4">
<h6 class="mb-2"><i class="el-icon-menu"></i> 当月事件列表</h6>
<el-empty v-if="monthEvents.length === 0" description="本月暂无事件" />
<el-timeline v-else>
<el-timeline-item v-for="(ev, idx) in monthEvents" :key="ev._id" :timestamp="ev.start | formatDateTime"
:color="isExpired(ev) ? '#F56C6C' : '#409EFF'">
<div>
<b>{{ ev.title }}</b>
<el-tag size="mini" style="margin-left:8px;">{{ typeText(ev.className) }}</el-tag>
<span style="font-size:13px;"> {{ ev.start | formatDateTime }} ~ {{ ev.end | formatDateTime }}</span>
<span v-if="ev.location" style="margin-left:8px;"><i class="el-icon-location-outline"></i>
{{ ev.location }}</span>
<el-tag v-if="ev.expire && ev.expire !== 'none'" size="mini" type="warning"
style="margin-left:8px;">到期后:{{ expireText(ev.expire) }}</el-tag>
</div>
<div style="margin-top:4px;">
<el-button size="mini" type="primary" icon="el-icon-edit" @click="openCreateModal('editor',idx.id)">编辑</el-button>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
<addCalendar ref="addCalendar" @refresh="getList"></addCalendar>
</div>
</template>
<script>
let idSeed = 1000;
const mockData = {
course: [{
id: 101,
title: '2025产业加速营',
start: '2025-06-04T09:00',
end: '2025-06-04T17:00',
location: 'A101',
teacher: '王教授',
description: '智能制造专题'
},
{
id: 102,
title: '2025人工智能专题课程',
start: '2025-06-17T09:00',
end: '2025-06-19T17:00',
location: 'A201',
teacher: 'AI专家组',
description: 'AI理论与实操'
}
],
activity: [{
id: 201,
title: '校友企业参访',
start: '2025-06-10T13:30',
end: '2025-06-10T17:00',
location: '高新区',
teacher: '李总监',
description: '新能源企业参访'
},
{
id: 202,
title: '校友论坛',
start: '2025-06-20T14:00',
end: '2025-06-20T17:30',
location: '报告厅',
teacher: '特邀嘉宾',
description: '数字经济新机遇'
}
],
workshop: [{
id: 301,
title: '创新创业移动课堂',
start: '2025-06-08T09:00',
end: '2025-06-10T17:00',
location: 'C301',
teacher: '创业导师团',
description: '创新创业实战训练'
}]
};
import addCalendar from "./components/addCalendar.vue"
import {
index
} from "@/api/calendars/index.js"
export default {
components:{
addCalendar
},
data() {
return {
dataType: 'course',
dataId: '',
dataList: [],
dataDateInfo: '',
calendarDate: new Date(),
events: [],
modalVisible: false,
modalTitle: '新建事件',
modalForm: {
type: 'course',
title: '',
start: '',
end: '',
location: '',
teacher: '',
description: '',
expire: 'none'
},
editingIndex: null,
eventDetail: null,
eventDetailVisible: false
}
},
computed: {
monthEvents() {
const now = this.calendarDate instanceof Date ? this.calendarDate : new Date(this.calendarDate);
const month = now.getMonth();
const year = now.getFullYear();
return this.events.filter(ev => {
const evDate = new Date(ev.start);
return evDate.getMonth() === month && evDate.getFullYear() === year;
});
},
selectMonth(){
console.log(this.calendarDate)
const now = this.calendarDate instanceof Date ? this.calendarDate : new Date(this.calendarDate);
const month = now.getMonth()+1<10?'0'+(now.getMonth()+1):now.getMonth()+1;
const year = now.getFullYear();
return year+'-'+month;
}
},
watch: {
dataType: 'onTypeChange'
},
created() {
this.onTypeChange();
this.getList()
},
methods: {
async getList(){
const res = await index({
month:this.selectMonth
})
},
onTypeChange() {
this.dataList = mockData[this.dataType] || [];
this.dataId = '';
this.dataDateInfo = '';
},
onDataChange() {
const item = (this.dataList || []).find(d => d.id === this.dataId);
if (item) {
this.dataDateInfo = `时间:${item.start.replace('T',' ')} ~ ${item.end.replace('T',' ')}`;
} else {
this.dataDateInfo = '';
}
},
fetchData() {
if (!this.dataId) return this.$message.warning('请选择要拉取的数据');
const item = (this.dataList || []).find(d => d.id === this.dataId);
if (item) {
this.events.push({
_id: ++idSeed,
title: item.title,
start: item.start,
end: item.end,
location: item.location,
teacher: item.teacher,
description: item.description,
className: `event-${this.dataType}`,
expire: 'none'
});
}
},
openCreateModal(type,id) {
if(id){
this.$refs.addCalendar.id = id
}
this.$refs.addCalendar.type = type
this.$refs.addCalendar.isShow = true
},
saveEvent() {
if (!this.modalForm.title || !this.modalForm.start || !this.modalForm.end) {
this.$message.error('请填写完整信息');
return;
}
const eventObj = {
_id: this.editingIndex !== null ? this.events[this.editingIndex]._id : ++idSeed,
title: this.modalForm.title,
start: this.modalForm.start,
end: this.modalForm.end,
location: this.modalForm.location,
teacher: this.modalForm.teacher,
description: this.modalForm.description,
className: `event-${this.modalForm.type}`,
expire: this.modalForm.expire
};
if (this.editingIndex !== null) {
this.$set(this.events, this.editingIndex, eventObj);
} else {
this.events.push(eventObj);
}
this.modalVisible = false;
},
editEvent(idx) {
const ev = this.monthEvents[idx];
this.modalTitle = '编辑事件';
this.modalForm = {
type: ev.className.replace('event-', ''),
title: ev.title,
start: ev.start,
end: ev.end,
location: ev.location,
teacher: ev.teacher,
description: ev.description,
expire: ev.expire || 'none'
};
this.editingIndex = this.events.findIndex(e => e._id === ev._id);
this.modalVisible = true;
},
eventsForDate(date) {
const d = new Date(date);
return this.events.filter(ev => {
const evDate = new Date(ev.start);
return evDate.getFullYear() === d.getFullYear() && evDate.getMonth() === d.getMonth() && evDate
.getDate() === d.getDate();
});
},
showEvent(ev) {
this.eventDetail = ev;
this.eventDetailVisible = true;
},
isExpired(ev) {
return new Date(ev.end) < new Date() && ev.expire === 'mark';
},
typeText(className) {
if (!className) return '';
const t = className.replace('event-', '');
if (t === 'course') return '课程';
if (t === 'activity') return '活动';
if (t === 'workshop') return '移动课堂';
return t;
},
expireText(val) {
if (val === 'mark') return '标记为已过期';
if (val === 'delete') return '自动删除';
if (val === 'remind') return '发送提醒';
return '无';
}
},
filters: {
formatDateTime(val) {
if (!val) return '';
return val.replace('T', ' ');
}
}
}
</script>
<style scoped>
.admin-calendar {
background: #f4f6fa;
min-height: 100vh;
}
.admin-header {
background: #fff;
padding: 20px 30px 10px 30px;
border-bottom: 1px solid #e9ecef;
font-size: 22px;
font-weight: 700;
color: #2c3e50;
letter-spacing: 2px;
}
.admin-main {
display: flex;
gap: 24px;
padding: 30px 30px 0 30px;
min-height: 90vh;
}
.admin-panel {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 24px 18px;
flex: 1 1 0;
min-width: 320px;
display: flex;
flex-direction: column;
gap: 18px;
}
.admin-panel-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
color: #1565c0;
}
.cell-content {
position: relative;
min-height: 24px;
}
.event-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #409EFF;
display: inline-block;
margin-right: 2px;
cursor: pointer;
}
.mt-4 {
margin-top: 24px;
}
.mb-2 {
margin-bottom: 8px;
}
.text-secondary {
color: #888;
}
@media (max-width: 1200px) {
.admin-main {
flex-direction: column;
gap: 18px;
padding: 18px 6px 0 6px;
}
}
</style>