校历优化

This commit is contained in:
biss committed 2026-09-29 09:33:25 +08:00
1 parent 0ef0d4d399
commit 4ef4164b00
6 files changed
+196 -39

No files matched your search

+74 -29
View File
@@ -121,6 +121,13 @@ function label(kind: number | string) {
function spans(item: CalendarEvent, day: string) {
return item.startsOn <= day && (item.endsOn ?? item.startsOn) >= day;
}
interface WeekEventSegment {
item: CalendarEvent;
id: string;
startColumn: number;
length: number;
lane: number;
}
const today = key(new Date()),
monthTitle = computed(
() => `${cursor.value.getFullYear()} 年 ${cursor.value.getMonth() + 1} 月`,
@@ -145,13 +152,40 @@ const days = computed(() => {
};
});
});
const weeks = computed(() =>
Array.from({ length: 6 }, (_, weekIndex) => {
const weekDays = days.value.slice(weekIndex * 7, weekIndex * 7 + 7);
const firstDay = weekDays[0].day;
const lastDay = weekDays[6].day;
const laneEnds: string[] = [];
const eventSegments: WeekEventSegment[] = events.value
.filter((item) => spans(item, firstDay) || spans(item, lastDay) || (item.startsOn < firstDay && (item.endsOn ?? item.startsOn) > lastDay))
.map((item) => {
const startsOn = item.startsOn > firstDay ? item.startsOn : firstDay;
const endsOn = (item.endsOn ?? item.startsOn) < lastDay ? (item.endsOn ?? item.startsOn) : lastDay;
return {
item,
startsOn,
endsOn,
startColumn: weekDays.findIndex((day) => day.day === startsOn) + 1,
length: Math.round((parse(endsOn).getTime() - parse(startsOn).getTime()) / 86400000) + 1,
};
})
.sort((left, right) => left.startsOn.localeCompare(right.startsOn) || right.length - left.length || left.item.title.localeCompare(right.item.title))
.map((segment) => {
const lane = laneEnds.findIndex((endsOn) => endsOn < segment.startsOn);
const assignedLane = lane === -1 ? laneEnds.length : lane;
laneEnds[assignedLane] = segment.endsOn;
return { ...segment, id: `${segment.item.id}-${weekIndex}`, lane: assignedLane };
});
return { days: weekDays, eventSegments };
}),
);
const monthEvents = computed(() =>
events.value.filter((item) => {
const date = parse(item.startsOn);
return (
date.getFullYear() === cursor.value.getFullYear() &&
date.getMonth() === cursor.value.getMonth()
);
const monthStart = key(new Date(cursor.value.getFullYear(), cursor.value.getMonth(), 1));
const monthEnd = key(new Date(cursor.value.getFullYear(), cursor.value.getMonth() + 1, 0));
return item.startsOn <= monthEnd && (item.endsOn ?? item.startsOn) >= monthStart;
}),
);
function shift(amount: number) {
@@ -238,7 +272,7 @@ async function syncHolidays() {
academicTermId: selectedTermId.value,
year: holidayYear.value,
});
ElMessage.success(`已同步 ${data.syncedCount} 条节假日与调休数据。`);
ElMessage.success(`已同步 ${data.syncedCount} 个节假日/调休日程,覆盖 ${data.sourceDayCount} 天。`);
holidayDialogVisible.value = false;
await load();
} finally {
@@ -352,8 +386,10 @@ onMounted(async () => {
>
</div>
<div class="calendar-grid">
<section v-for="(week, weekIndex) in weeks" :key="weekIndex" class="calendar-week">
<div class="week-days">
<article
v-for="day in days"
v-for="day in week.days"
:key="day.day"
:class="[
'calendar-day',
@@ -365,25 +401,20 @@ onMounted(async () => {
]"
>
<div class="day-number">{{ day.date.getDate() }}</div>
<button
v-for="item in day.events.slice(0, 3)"
:key="item.id"
type="button"
:class="[
'calendar-event',
kindKey(item.kind),
{ due: due(item) },
]"
:title="item.title"
@click="canManage && open(item)"
>
<span>{{
item.title.replace("(法定节假日)", "")
}}</span></button
><span v-if="day.events.length > 3" class="more-events"
>+{{ day.events.length - 3 }} 项</span
>
</article>
</div>
<div v-if="week.eventSegments.length" class="week-events">
<button
v-for="segment in week.eventSegments"
:key="segment.id"
type="button"
:style="{ gridColumn: `${segment.startColumn} / span ${segment.length}`, gridRow: segment.lane + 1 }"
:class="['calendar-event', kindKey(segment.item.kind), { due: due(segment.item) }]"
:title="segment.item.title"
@click="canManage && open(segment.item)"
><span>{{ segment.item.title.replace("(法定节假日)", "") }}</span></button>
</div>
</section>
</div>
</div>
<aside class="month-agenda">
@@ -627,7 +658,8 @@ onMounted(async () => {
background: #fff;
}
.weekdays,
.calendar-grid {
.week-days,
.week-events {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
@@ -645,11 +677,20 @@ onMounted(async () => {
.weekdays span:nth-last-child(-n + 2) {
color: #a45a55;
}
.calendar-week {
border-bottom: 1px solid #e4ecee;
}
.calendar-week:last-child {
border-bottom: 0;
}
.week-events {
gap: 3px 0;
padding: 0 8px 8px;
}
.calendar-day {
min-height: 120px;
padding: 8px;
border-right: 1px solid #e4ecee;
border-bottom: 1px solid #e4ecee;
background: #fff;
}
.calendar-day:nth-child(7n) {
@@ -684,7 +725,8 @@ onMounted(async () => {
display: block;
overflow: hidden;
width: 100%;
margin: 3px 0;
min-width: 0;
margin: 0;
padding: 3px 5px;
color: #315966;
text-overflow: ellipsis;
@@ -847,9 +889,12 @@ onMounted(async () => {
margin-left: 0;
}
.calendar-day {
min-height: 88px;
min-height: 70px;
padding: 5px;
}
.week-events {
padding: 0 5px 5px;
}
.calendar-event {
padding: 2px 3px;
font-size: 10px;