课程日历

dev
lion 3 months ago
parent 129f38d53c
commit 66a427778d

@ -5,5 +5,7 @@ ENV='development'
VUE_APP_BASE_API=https://suzhoukeji-test.ali251.langye.net
VUE_APP_UPLOAD_API=https://suzhoukeji-test.ali251.langye.net/api/admin/upload-file
VUE_APP_PRO_API = https://www.sstbc.com
#VUE_APP_BASE_API = https://wx.sstbc.com
#VUE_APP_UPLOAD_API = https://wx.sstbc.com/api/admin/upload-file

@ -6,6 +6,8 @@ ENV = 'production'
VUE_APP_BASE_API=https://suzhoukeji-test.ali251.langye.net
VUE_APP_UPLOAD_API=https://suzhoukeji-test.ali251.langye.net/api/admin/upload-file
VUE_APP_PRO_API =https://www.sstbc.com
#VUE_APP_BASE_API = https://wx.sstbc.com
#VUE_APP_UPLOAD_API = https://wx.sstbc.com/api/admin/upload-file

@ -7,3 +7,4 @@ ENV = 'staging'
VUE_APP_BASE_API=https://suzhoukeji-test.ali251.langye.net
VUE_APP_UPLOAD_API=https://suzhoukeji-test.ali251.langye.net/api/admin/upload-file
VUE_APP_PRO_API =https://www.sstbc.com

@ -21,6 +21,7 @@
"axios": "0.18.1",
"core-js": "3.6.5",
"echarts": "^4.2.1",
"echarts-gl": "^1.1.1",
"element-ui": "2.15.13",
"file-saver": "^2.0.5",
"js-cookie": "2.2.0",

@ -0,0 +1,10 @@
import request from '@/utils/request'
export function home(params,isLoading) {
return request({
url: '/api/admin/other/home',
method: 'get',
params,
isLoading: true
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@ -1,5 +1,5 @@
<template>
<section class="app-main">
<section class="app-main" :style="{'padding':key.includes('/dashboard')?'0!important':''}">
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>
@ -17,7 +17,7 @@ export default {
}
</script>
<style scoped>
<style scoped lang="scss">
.app-main {
/*50 = navbar */
min-height: calc(100vh - 50px);

@ -3,7 +3,7 @@
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<div :class="{'fixed-header':fixedHeader}" :style="{'display':key.includes('/dashboard')?'none':''}">
<navbar />
</div>
<app-main />
@ -24,6 +24,9 @@ export default {
},
mixins: [ResizeMixin],
computed: {
key() {
return this.$route.path
},
sidebar() {
return this.$store.state.app.sidebar
},

@ -65,7 +65,7 @@
</div>
</div>
</template>
<template v-slot:date>
<!-- <template v-slot:date>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>日期
@ -76,7 +76,7 @@
</el-date-picker>
</div>
</div>
</template>
</template> -->
<template v-slot:start_time>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
@ -107,14 +107,13 @@
</div>
</div>
</template>
<template v-slot:content>
<template v-slot:content v-if="form.type===3 || form.type===4">
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>内容
</div>
<div class="xy-table-item-content">
<my-tinymce v-if="showTinymce" @input="saveContent" :value="form.content"></my-tinymce>
</div>
</div>
</template>
@ -190,7 +189,7 @@
course_content_id: '',
title: '',
url: '',
date: '',
// date: '',
start_time: '',
end_time: '',
content: '',

@ -0,0 +1,173 @@
<template>
<div class="admin-calendar">
<div class="admin-main">
<!-- 数据拉取区 -->
<div class="admin-panel" style="max-width: 350px;">
<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"></div>
</div>
</template>
</el-calendar>
<div class="mt-4">
<h3 class="mb-2"><i class="el-icon-menu"></i> 当月事件列表</h3>
<el-empty v-if="list.length === 0" description="本月暂无事件" />
<el-timeline v-else>
<el-timeline-item v-for="ev in list" :key="ev._id" :timestamp="ev.start | formatDateTime"
:color="'#409EFF'">
<div>
<b>{{ ev.title }}</b>
<el-tag size="mini" style="margin-left:8px;">{{ typeText(ev.type) }}</el-tag>
<span style="font-size:13px;"> {{ ev.start_time | formatDateTime }} ~ {{ ev.end_time | formatDateTime }}</span>
</div>
<div style="margin-top:4px;">
<el-button size="mini" type="primary" icon="el-icon-edit" @click="openCreateModal('editor',ev.id)"></el-button>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
<addCalendar ref="addCalendar" @refresh="getList"></addCalendar>
</div>
</template>
<script>
import addCalendar from './components/addCalendar.vue'
import {
index
} from '@/api/calendars/index.js'
export default {
components: {
addCalendar
},
data() {
return {
list: [],
calendarDate: new Date()
}
},
computed: {
selectMonth() {
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
}
},
created() {
this.getList()
},
methods: {
async getList() {
const res = await index({
month: this.selectMonth
})
this.list = res
},
openCreateModal(type, id) {
if (type === 'editor') {
this.$refs.addCalendar.id = id
}
this.$refs.addCalendar.type = type
this.$refs.addCalendar.isShow = true
},
eventsForDate(date) {
const d = new Date(date)
return this.list.filter(ev => {
const evDate = new Date(ev.start_time)
return evDate.getFullYear() === d.getFullYear() && evDate.getMonth() === d.getMonth() && evDate.getDate() === d.getDate()
})
},
typeText(className) {
if (!className) return ''
if (className === 1) return '课程'
if (className === 3) return '自定义事件'
if (className === 4) return '资讯'
return ''
}
},
filters: {
formatDateTime(val) {
if (!val) return ''
return val.replace('T', ' ')
}
}
}
</script>
<style scoped>
.admin-calendar {
background: #f4f6fa;
min-height: 100vh;
}
.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;
}
@media (max-width: 1200px) {
.admin-main {
flex-direction: column;
gap: 18px;
padding: 18px 6px 0 6px;
}
}
</style>

@ -1,67 +1,44 @@
<template>
<div class="admin-calendar">
<!-- 顶部操作区 -->
<div class="admin-header">
<i class="el-icon-date"></i> 课程日历管理
<el-button type="success" icon="el-icon-plus" @click="openCreateModal('add')"></el-button>
</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">
<div class="calendar-panel">
<div class="calendar-wrapper">
<el-calendar v-model="calendarDate" :first-day-of-week="7">
<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 v-for="ev in eventsForDate(date)" :key="ev._id" style="">
{{ev.title}}
<span class="date-number">{{ date.getDate() }}</span>
<div class="event-list">
<div
v-for="ev in eventsForDate(date)"
:key="ev._id"
:class="['event-item', getEventClass(ev, date)]"
:title="getEventTooltip(ev, date)"
@click.stop="openCreateModal('editor', ev.id)"
>
<span class="event-title">{{ ev.title }}</span>
</div>
</div>
</div>
</template>
</el-calendar>
<!-- 连续事件覆盖层 -->
<div class="continuous-events-overlay">
<div
v-for="event in getContinuousEvents()"
:key="`continuous-${event.id}-${event.weekStart}`"
:class="['continuous-event', `event-type-${event.type || 'default'}`]"
:style="getContinuousEventStyle(event)"
:title="getEventTooltip(event, new Date(event.weekStart))"
@click.stop="openCreateModal('editor', event.id)"
>
{{ event.title }}
</div>
</template>
</el-calendar>
<div class="mt-4">
<h3 class="mb-2"><i class="el-icon-menu"></i> 当月事件列表</h3>
<el-empty v-if="list.length === 0" description="本月暂无事件" />
<el-timeline v-else>
<el-timeline-item v-for="(ev, idx) in list" :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.type) }}</el-tag>
<span style="font-size:13px;"> {{ ev.start_time | formatDateTime }} ~ {{ ev.end_time | 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',ev.id)"></el-button>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
@ -70,293 +47,642 @@
</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 addCalendar from './components/addCalendar.vue'
import {
index
} from "@/api/calendars/index.js"
} from '@/api/calendars/index.js'
export default {
components:{
components: {
addCalendar
},
data() {
return {
list:[],
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
list: [],
calendarDate: new Date()
}
},
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;
selectMonth() {
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'
calendarDate: {
handler() {
this.getList()
},
deep: true
}
},
created() {
this.onTypeChange();
this.getList()
},
methods: {
async getList(){
async getList() {
const res = await index({
month:this.selectMonth
month: this.selectMonth
})
this.list = res
},
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) {
console.log("type",type,id)
if(type == 'editor'){
openCreateModal(type, id) {
if (type === 'editor') {
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;
eventsForDate(date) {
const d = new Date(date)
return this.list.filter(ev => {
const startDate = this.parseDateTime(ev.start_time)
// end_timestart_time
if (!ev.end_time) {
const currentDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
return currentDate.getTime() === eventStartDate.getTime()
}
const endDate = this.parseDateTime(ev.end_time)
//
const isMultiDay = startDate.getDate() !== endDate.getDate() ||
startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear()
//
if (isMultiDay) {
return false
}
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);
//
const currentDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
return currentDate.getTime() === eventStartDate.getTime()
})
},
getEventClass(event, date) {
const startDate = new Date(event.start_time)
const currentDate = new Date(date)
// end_time
if (!event.end_time) {
return `single-day event-type-${event.type || 'default'}`
}
const endDate = new Date(event.end_time)
//
const isMultiDay = startDate.getDate() !== endDate.getDate() ||
startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear()
if (!isMultiDay) {
return `single-day event-type-${event.type || 'default'}`
}
//
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
const eventEndDate = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
if (currentDateOnly.getTime() === eventStartDate.getTime()) {
return `multi-day-start event-type-${event.type || 'default'}`
} else if (currentDateOnly.getTime() === eventEndDate.getTime()) {
return `multi-day-end event-type-${event.type || 'default'}`
} else {
return `multi-day-middle event-type-${event.type || 'default'}`
}
},
getEventTooltip(event, date) {
const startDate = new Date(event.start_time)
// end_time
if (!event.end_time) {
return `${event.title}\n时间${this.formatDateTime(event.start_time)}`
}
const endDate = new Date(event.end_time)
const isMultiDay = startDate.getDate() !== endDate.getDate() ||
startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear()
if (isMultiDay) {
return `${event.title}\n时间${this.formatDateTime(event.start_time)} ~ ${this.formatDateTime(event.end_time)}`
} else {
this.events.push(eventObj);
return `${event.title}\n时间${this.formatDateTime(event.start_time)} ~ ${this.formatDateTime(event.end_time)}`
}
},
getContinuousEvents() {
const FIRST_DOW = 0 // 0=Sunday to match :first-day-of-week="7" on el-calendar
const continuousEvents = []
//
const multiDayEvents = this.list.filter(ev => {
if (!ev.end_time) return false
const s = this.parseDateTime(ev.start_time)
const e = this.parseDateTime(ev.end_time)
return s.toDateString() !== e.toDateString()
})
const currentMonth = this.calendarDate.getMonth()
const currentYear = this.calendarDate.getFullYear()
const monthStart = new Date(currentYear, currentMonth, 1)
const monthEnd = new Date(currentYear, currentMonth + 1, 0)
function getWeekStart(date) {
const d = new Date(date)
const jsDow = d.getDay() // 0..6 (Sun..Sat)
const offset = (jsDow - FIRST_DOW + 7) % 7
d.setDate(d.getDate() - offset)
return d
}
multiDayEvents.forEach(ev => {
const eventStart = this.parseDateTime(ev.start_time)
const eventEnd = this.parseDateTime(ev.end_time)
if (eventEnd < monthStart || eventStart > monthEnd) return
// Clamp to month range so we only render in current month viewport
const clampedStart = eventStart < monthStart ? monthStart : eventStart
const clampedEnd = eventEnd > monthEnd ? monthEnd : eventEnd
let cursor = getWeekStart(clampedStart)
while (cursor <= clampedEnd) {
const weekStart = new Date(cursor)
const weekEnd = new Date(cursor)
weekEnd.setDate(weekEnd.getDate() + 6)
// Segment inside this week
const segStart = clampedStart > weekStart ? clampedStart : weekStart
const segEnd = clampedEnd < weekEnd ? clampedEnd : weekEnd
if (segStart <= segEnd) {
const startCol = (segStart.getDay() - FIRST_DOW + 7) % 7
const endCol = (segEnd.getDay() - FIRST_DOW + 7) % 7
const spanCols = endCol - startCol + 1
continuousEvents.push({
...ev,
weekStartISO: weekStart.toISOString(),
segStartISO: segStart.toISOString(),
segEndISO: segEnd.toISOString(),
startCol,
spanCols,
endCol
})
}
cursor.setDate(cursor.getDate() + 7)
}
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.list.filter(ev => {
const evDate = new Date(ev.start_time);
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';
})
// lane
const byWeek = {}
continuousEvents.forEach(seg => {
const key = seg.weekStartISO
if (!byWeek[key]) byWeek[key] = []
byWeek[key].push(seg)
})
Object.values(byWeek).forEach(segs => {
//
segs.sort((a, b) => (a.startCol - b.startCol) || (b.spanCols - a.spanCols))
const laneEndCols = [] //
segs.forEach(seg => {
let placed = false
for (let i = 0; i < laneEndCols.length; i += 1) {
if (laneEndCols[i] < seg.startCol) {
seg.laneIndex = i
laneEndCols[i] = seg.endCol
placed = true
break
}
}
if (!placed) {
seg.laneIndex = laneEndCols.length
laneEndCols.push(seg.endCol)
}
})
})
return continuousEvents
},
getContinuousEventStyle(event) {
const FIRST_DOW = 0
const currentMonth = this.calendarDate.getMonth()
const currentYear = this.calendarDate.getFullYear()
const firstDay = new Date(currentYear, currentMonth, 1)
// 7
const adjStart = new Date(event.segStartISO)
adjStart.setDate(adjStart.getDate() + 7)
const msPerDay = 1000 * 60 * 60 * 24
const daysFromFirstOfMonth = Math.floor((adjStart - firstDay) / msPerDay)
const firstDayOffset = (firstDay.getDay() - FIRST_DOW + 7) % 7
const totalDaysFromCalendarStart = daysFromFirstOfMonth + firstDayOffset
const weekRow = Math.floor(totalDaysFromCalendarStart / 7)
// 使
const startColAdjusted = (adjStart.getDay() - FIRST_DOW + 7) % 7
const cellWidth = 100 / 7
const cellHeight = 100
const headerHeight = 50
const dateNumberHeight = 25
const eventHeight = 16
const eventSpacing = 2
const verticalOffset = (event.laneIndex || 0) * (eventHeight + eventSpacing)
return {
position: 'absolute',
left: `calc(${startColAdjusted * cellWidth}% + 2px)`,
top: `${headerHeight + weekRow * cellHeight + dateNumberHeight + verticalOffset}px`,
width: `calc(${event.spanCols * cellWidth}% - 4px)`,
height: `${eventHeight}px`,
zIndex: 1000,
background: `linear-gradient(90deg, ${this.getEventTypeColor(event.type)} 0%, ${this.darkenColor(this.getEventTypeColor(event.type))} 100%)`,
borderRadius: '3px',
fontSize: '11px',
lineHeight: `${eventHeight}px`,
color: 'white',
padding: '0 4px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
cursor: 'pointer',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)'
}
},
getEventStyle(event, date) {
const startDate = new Date(event.start_time)
const currentDate = new Date(date)
// end_time使
if (!event.end_time) {
return {}
}
const endDate = new Date(event.end_time)
//
const isMultiDay = startDate.getDate() !== endDate.getDate() ||
startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear()
if (!isMultiDay) {
return {}
}
//
const currentWeekStart = new Date(currentDate)
const dayOfWeek = currentDate.getDay()
currentWeekStart.setDate(currentDate.getDate() - dayOfWeek)
//
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
const eventEndDate = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
//
const weekStart = new Date(currentWeekStart)
const weekEnd = new Date(currentWeekStart)
weekEnd.setDate(weekEnd.getDate() + 6)
//
const eventWeekStart = eventStartDate < weekStart ? weekStart : eventStartDate
const eventWeekEnd = eventEndDate > weekEnd ? weekEnd : eventEndDate
//
if (currentDateOnly < eventWeekStart || currentDateOnly > eventWeekEnd) {
return {}
}
//
if (currentDateOnly.getTime() === eventWeekStart.getTime()) {
const startDayOfWeek = eventWeekStart.getDay()
const endDayOfWeek = eventWeekEnd.getDay()
const spanDays = endDayOfWeek - startDayOfWeek + 1
//
const bgColor = this.getEventTypeColor(event.type)
return {
position: 'absolute',
left: '0',
top: '1px',
width: `calc(${spanDays * 100}% - 2px)`,
zIndex: 10,
background: `linear-gradient(90deg, ${bgColor} 0%, ${this.darkenColor(bgColor)} 100%)`,
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.2)',
border: '1px solid rgba(255, 255, 255, 0.3)',
borderRadius: '3px'
}
} else if (currentDateOnly > eventWeekStart && currentDateOnly <= eventWeekEnd) {
//
return {
opacity: '0',
pointerEvents: 'none'
}
}
return {}
},
getEventDisplayTitle(event, date) {
const startDate = new Date(event.start_time)
const currentDate = new Date(date)
// end_time
if (!event.end_time) {
return event.title
}
const endDate = new Date(event.end_time)
//
const isMultiDay = startDate.getDate() !== endDate.getDate() ||
startDate.getMonth() !== endDate.getMonth() ||
startDate.getFullYear() !== endDate.getFullYear()
if (!isMultiDay) {
return event.title
}
//
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
//
const currentWeekStart = new Date(currentDate)
const dayOfWeek = currentDate.getDay()
currentWeekStart.setDate(currentDate.getDate() - dayOfWeek)
const weekStart = new Date(currentWeekStart)
const eventWeekStart = eventStartDate < weekStart ? weekStart : eventStartDate
if (currentDateOnly.getTime() === eventWeekStart.getTime()) {
return event.title
}
return '' //
},
getEventTypeColor(type) {
//
const colorMap = {
1: '#67C23A', // - 绿
2: '#409EFF', // -
3: '#E6A23C', // -
4: '#F56C6C', // -
5: '#909399', // -
default: '#409EFF' //
}
return colorMap[type] || colorMap.default
},
// Safari/
parseDateTime(dateTimeStr) {
if (!dateTimeStr) return null
// "YYYY-MM-DD HH:mm:ss" "YYYY-MM-DD"
const [datePart, timePart = '00:00:00'] = dateTimeStr.trim().split(/\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)
},
darkenColor(color) {
//
const colorMap = {
'#67C23A': '#5CB85C', // 绿
'#409EFF': '#337ecc', //
'#E6A23C': '#D4952B', //
'#F56C6C': '#E85555', //
'#909399': '#73767A' //
}
return colorMap[color] || '#337ecc'
},
formatDateTime(val) {
if (!val) return ''
return val.replace('T', ' ')
},
typeText(className) {
if (!className) return '';
if (className === 1) return '课程';
if (className === 3) return '自定义事件';
if (className === 4) return '资讯';
return t;
},
expireText(val) {
if (val === 'mark') return '标记为已过期';
if (val === 'delete') return '自动删除';
if (val === 'remind') return '发送提醒';
return '无';
if (!className) return ''
if (className === 1) return '课程'
if (className === 3) return '自定义事件'
if (className === 4) return '资讯'
return ''
}
},
filters: {
formatDateTime(val) {
if (!val) return '';
return val.replace('T', ' ');
if (!val) return ''
return val.replace('T', ' ')
}
}
}
</script>
<style scoped>
<style scoped lang="scss">
::v-deep .el-calendar__body{
padding-left:0;
padding-right:0;
}
.admin-calendar {
background: #f4f6fa;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.admin-header {
background: #fff;
background: transparent;
padding: 20px 30px 10px 30px;
border-bottom: 1px solid #e9ecef;
font-size: 22px;
font-weight: 700;
color: #2c3e50;
letter-spacing: 2px;
border-bottom: none;
box-shadow: none;
}
.admin-main {
display: flex;
gap: 24px;
padding: 30px 30px 0 30px;
min-height: 90vh;
flex: 1;
padding: 20px 30px;
overflow: hidden;
}
.admin-panel {
.calendar-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;
padding: 24px;
height: 100%;
overflow: auto;
}
.calendar-wrapper {
position: relative;
}
.continuous-events-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 100;
}
.continuous-event {
pointer-events: auto;
transition: all 0.2s ease;
}
.continuous-event:hover {
transform: translateY(-1px);
filter: brightness(1.1);
z-index: 101;
}
/* Element UI 日历样式覆盖 */
.calendar-panel ::v-deep .el-calendar-table {
overflow: visible;
}
.calendar-panel ::v-deep .el-calendar-table .el-calendar-day {
position: relative;
overflow: visible;
height: auto;
min-height: 100px;
padding: 4px;
}
.calendar-panel ::v-deep .el-calendar-table td {
position: relative;
overflow: visible;
border: 1px solid #ebeef5;
}
.calendar-panel ::v-deep .el-calendar-table tbody tr {
overflow: visible;
}
.calendar-panel ::v-deep .el-calendar-table tbody {
overflow: visible;
}
.cell-content {
position: relative;
min-height: 100px;
display: flex;
flex-direction: column;
gap: 18px;
padding: 2px;
overflow: visible;
}
.admin-panel-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
color: #1565c0;
.date-number {
font-weight: bold;
color: #333;
margin-bottom: 2px;
position: relative;
z-index: 1;
}
.cell-content {
.event-list {
flex: 1;
overflow: visible;
position: relative;
min-height: 24px;
z-index: 5;
}
.event-dot {
width: 8px;
height: 8px;
border-radius: 50%;
.event-item {
font-size: 11px;
line-height: 14px;
padding: 1px 3px;
margin: 1px 0;
background: #409EFF;
display: inline-block;
margin-right: 2px;
color: white;
border-radius: 3px;
cursor: pointer;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
transition: background-color 0.2s;
position: relative;
}
.event-title {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.event-item:hover {
background: #337ecc;
}
/* 不同事件类型的颜色 */
.event-item.event-type-1 {
background: #67C23A; /* 课程 - 绿色 */
}
.event-item.event-type-2 {
background: #409EFF; /* 会议 - 蓝色 */
}
.event-item.event-type-3 {
background: #E6A23C; /* 自定义事件 - 橙色 */
}
.event-item.event-type-4 {
background: #F56C6C; /* 资讯 - 红色 */
}
.event-item.event-type-5 {
background: #909399; /* 其他 - 灰色 */
}
.event-item.event-type-default {
background: #409EFF; /* 默认 - 蓝色 */
}
/* 悬停效果 */
.event-item.event-type-1:hover {
background: #5CB85C;
}
.event-item.event-type-2:hover {
background: #337ecc;
}
.event-item.event-type-3:hover {
background: #D4952B;
}
.event-item.event-type-4:hover {
background: #E85555;
}
.event-item.event-type-5:hover {
background: #73767A;
}
.event-item.event-type-default:hover {
background: #337ecc;
}
/* 连续事件的特殊样式 */
.event-item[style*="position: absolute"] {
border-radius: 3px !important;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.event-item[style*="position: absolute"]:hover {
transform: translateY(-1px);
filter: brightness(1.1);
}
.mt-4 {
@ -367,15 +693,17 @@
margin-bottom: 8px;
}
.text-secondary {
color: #888;
}
@media (max-width: 1200px) {
.admin-header {
padding: 15px 20px;
}
.admin-main {
flex-direction: column;
gap: 18px;
padding: 18px 6px 0 6px;
padding: 15px 20px;
}
.calendar-panel {
padding: 16px;
}
}
</style>

@ -51,6 +51,19 @@
</div>
</div>
</template>
<template v-slot:url v-if="active===0">
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>资讯链接
</div>
<div class="xy-table-item-content">
<el-select v-model="form.url" placeholder="请选择" clearable style="width: 100%;">
<el-option v-for="item in false_or_true" :key="item.id" :label="item.value" :value="item.id">
</el-option>
</el-select>
</div>
</div>
</template>
<template v-slot:year v-if="active===0">
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
@ -111,7 +124,7 @@
</template>
<!-- <template v-slot:status v-if="active===0">
<!-- <template v-slot:status v-if="active===0">
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>状态
@ -245,8 +258,9 @@
<Button v-if="formList.length>0" ghost type="primary" @click="editForm('editor')"></Button>
<Button v-else ghost type="primary" @click="editForm('add')"></Button>
</template>
<el-form style="min-height: 300px;display: flex;flex-wrap: wrap;justify-content: space-between;padding: 0 4%;" label-width="80px"
label-position="top" size="small">
<el-form
style="min-height: 300px;display: flex;flex-wrap: wrap;justify-content: space-between;padding: 0 4%;"
label-width="80px" label-position="top" size="small">
<!-- <el-divider>基础字段</el-divider>
<el-form-item :label="i.name" :required="
i.rule ? !!i.rule.includes('required') : false
@ -274,7 +288,7 @@
<Button ghost type="primary" v-if="active===1" @click="active=0"></Button>
<!-- <Button ghost type="primary" v-if="active===0" @click="next"></Button> -->
<Button type="primary" ghost @click="checkSubmit" v-if="active===0"></Button>
<Button type="primary" ghost v-if="active===1" @click="closeSubmit(0)" >保存待发</Button>
<Button type="primary" ghost v-if="active===1" @click="closeSubmit(0)"></Button>
<Button type="primary" v-if="active===1" @click="closeSubmit(1)"></Button>
</template>
@ -310,6 +324,7 @@
} from "@/utils";
import applyForm from "../components/applyForm.vue";
import formSlotRender from "@/views/system/components/formSlotRender.vue";
import axios from 'axios'
export default {
mixins: [myMixins, formMixins],
components: {
@ -319,7 +334,7 @@
data() {
return {
isShow: false,
showTips:false,
showTips: false,
type: 'add',
active: 0,
id: '',
@ -334,7 +349,8 @@
step: '', //
type: '',
name: '',
is_virtual:0, //01
is_virtual: 0, //01
url: '',
year: '',
dateRange: '',
total: '',
@ -389,11 +405,37 @@
sort_name: 'sort',
sort_type: 'ASC'
},
formList: []
formList: [],
zxkeyword:'',
zixunList:[]
}
},
created() {},
created() {
this.getZxList()
},
methods: {
async getZxList() {
const proUrl = `${process.env.VUE_APP_PRO_API}`
var baseUrl = proUrl + '/e/extend/news.php'
try {
// axios.get()
const response = await axios.get(baseUrl, {
params: {
page:1,
pagesize:5,
keyword:this.zxkeyword
}, // GET URL
timeout: 5000 //
});
//
this.zixunList = response.data;
console.log('GET 请求成功:', response.data);
} catch (error) {
//
console.error('GET 请求失败:', error);
alert('请求失败:' + (error.response?.data?.msg || '网络错误'));
}
},
changeType(e) {
// if (e == 1) {
@ -476,7 +518,7 @@
submit() {
if (this.id) {
this.form.id = this.id
}else{
} else {
this.form.id = ''
}
@ -515,7 +557,7 @@
})
},
//
saveSubmit(){
saveSubmit() {
this.form.id = this.id
this.form.status = 1
save({
@ -533,23 +575,23 @@
},
closeSubmit(status) {
//
if(status==1){
if(this.formList.length<1){
if (status == 1) {
if (this.formList.length < 1) {
this.$message({
type:'warning',
message:'需要先创建报名表单才可以发布',
duration:2000
type: 'warning',
message: '需要先创建报名表单才可以发布',
duration: 2000
})
return
}
//
if(this.form.status===1){
if (this.form.status === 1) {
this.$emit('refresh')
this.isShow = false
}else{
} else {
this.showTips = true
}
}else{
} else {
this.form.id = this.id
this.form.status = 0
save({
@ -585,7 +627,7 @@
this.form.status = res.status ? res.status : 0
this.form.is_arrange = res.is_arrange ? res.is_arrange : 0
this.form.is_fee = res.is_fee ? res.is_fee : 0
this.form.is_virtual = res.is_virtual?res.is_virtual:0
this.form.is_virtual = res.is_virtual ? res.is_virtual : 0
this.form.show_txl = res.show_txl === 0 ? 0 : 1
this.form.show_mobile = res.show_mobile ? res.show_mobile : 0
this.showTinymce = true
@ -614,7 +656,7 @@
//
editForm(type, id) {
// this.$refs.applyForm.formList = deepCopy(this.formList)
if(type==='add'){
if (type === 'add') {
this.$refs.applyForm.showStartTips = true
}
this.$refs.applyForm.course_id = this.id
@ -646,7 +688,8 @@
step: '', //
type: '',
name: '',
is_virtual:0,
is_virtual: 0,
url: '',
year: '',
dateRange: '',
total: '',

File diff suppressed because it is too large Load Diff

@ -0,0 +1,92 @@
<template>
<div class="jsc" :style="bgStyle">
</div>
</template>
<script>
import echarts from "echarts"
import PanelGroup from './components/PanelGroup'
import {
getChartsHome
} from "../../api/dashboard.js"
export default {
components: {
PanelGroup
},
data() {
return {
screenWidth: 0,
screenHeight: 0,
bgStyle: {
width: 0,
height: 0
},
col: '',
line: '',
business_data: [],
collect_data: [],
list: {},
customerArr: [],
orderArr: [],
chartData: {},
}
},
watch: {
// chartData(val, newval) {
// if (newval){
// this.init();
// }
// }
},
created() {
},
mounted() {
this.$store.dispatch('app/closeSideBar', {
withoutAnimation: false
})
this.calculateScreenSize()
},
destroyed() {
window.onresize = null
},
methods: {
calculateScreenSize() {
this.screenWidth = window.innerWidth
this.screenHeight = window.innerHeight
this.bgStyle = {
width: this.screenWidth - 56 + 'px',
height: this.screenHeight + 'px',
position: 'relative',
left: '-20px',
top: '-50px',
'z-index': 999
}
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .app-main {
padding: 0 !important
}
::v-deep .hideSidebar .fixed-header {
display: none !important;
}
.jsc {
height: 100%;
background: url(../../assets/dashboard.png) no-repeat center top;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
padding-top: 0;
}
</style>

File diff suppressed because it is too large Load Diff

@ -30,7 +30,7 @@
<template v-slot:company_name>
<el-table-column align='left' label="企业名称" width="240" header-align="center">
<template slot-scope="scope">
<div @click="toQicc(scope.row.overseas_experience)" style="color:blue;cursor: pointer;text-decoration: underline;">
<div @click="toQicc(scope.row.company_name)" style="color:blue;cursor: pointer;text-decoration: underline;">
{{scope.row.company_name}}
</div>
</template>
@ -39,7 +39,7 @@
<template v-slot:company_legal_representative>
<el-table-column align='center' label="法人代表" width="120" header-align="center">
<template slot-scope="scope">
<div @click="toQicc(scope.row.overseas_experience)" style="color:blue;cursor: pointer;text-decoration: underline;">
<div @click="toQicc(scope.row.company_name)" style="color:blue;cursor: pointer;text-decoration: underline;">
{{scope.row.company_legal_representative}}
</div>
</template>
@ -48,7 +48,7 @@
<template v-slot:company_shareholder>
<el-table-column align='center' label="股东信息" width="120" header-align="center">
<template slot-scope="scope">
<div @click="toQicc(item.overseas_experience)" style="color:blue;cursor: pointer;text-decoration: underline;">
<div @click="toQicc(item.company_name)" style="color:blue;cursor: pointer;text-decoration: underline;">
{{scope.row.company_shareholder}}
</div>
</template>
@ -77,28 +77,24 @@
<template v-slot:users>
<el-table-column align='left' label="校友信息" width="240" header-align="center">
<template slot-scope="scope">
<div v-if="scope.row.id===1">
<div>
校友王磊
</div>
<div>
职务
</div>
<div>
课程2025产业加速营 | 具身智能机器人专赛暨具身智能极客营
</div>
</div>
<div v-if="scope.row.id===2">
<div>
校友周肖虹
</div>
<div>
职务总裁
</div>
<div>
课程2024高研班 | 第六期高级科创人才研修班
<template v-if="scope.row.users && scope.row.users.length>0">
<div v-for="item in scope.row.users">
<div>
校友{{item.name}}
</div>
<div>
职务{{item.company_position}}
</div>
<div v-if="item.course_signs && item.course_signs.length>0">
课程
<template v-for="(item1,index) in item.course_signs">
<span>{{item1.course.name}}</span>
<span v-if="(index+1)<item.course_signs.length"></span>
</template>
</div>
</div>
</div>
</template>
</template>
</el-table-column>
@ -200,10 +196,10 @@
this.getList()
},
methods: {
toQicc(overseas_experience) {
if(overseas_experience){
window.open(overseas_experience, '_blank')
toQicc(company_name) {
var url = 'https://www.qcc.com/web/search?key='
if(company_name){
window.open(url+company_name, '_blank')
}
// 'https://www.qcc.com/firm/ffb3379dc6b254336ecd31fc20d1dd30.html'
// https://www.qcc.com/firm/0a07162e2b34a5ee8046d46a1a446ed2.html

@ -56,7 +56,14 @@ module.exports = {
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: process.env.VUE_APP_BASE_API
}
}
},
[process.env.VUE_APP_PRO_API]: {
target: process.env.VUE_APP_PRO_API,
changeOrigin: true, //配置跨域
pathRewrite: {
['^' + process.env.VUE_APP_PRO_API]: process.env.VUE_APP_PRO_API
}
},
}
},
configureWebpack: {

Loading…
Cancel
Save