fix: dynamic time slot columns from events + detailed event blocks

- Only show columns for time slots that have actual courses
- Event blocks show: lesson period, time range, teacher, students
- Horizontal scroll when many slot columns

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-02 09:24:25 +08:00
parent 0fa623b73c
commit f54705dd5b
2 changed files with 62 additions and 13 deletions
+56 -13
View File
@@ -1,10 +1,14 @@
<template>
<div class="matrix-wrapper">
<div class="matrix-grid">
<div v-if="!timeSlots.length" class="matrix-empty">
本周暂无排课数据
</div>
<div v-else class="matrix-grid" :style="gridStyle">
<!-- Corner -->
<div class="grid-cell corner-cell"></div>
<!-- Header: time slots -->
<!-- Header: time slots (动态) -->
<div
v-for="slot in timeSlots"
:key="slot.key"
@@ -37,8 +41,9 @@
@click.stop="onEventClick(ev)"
>
<div class="ev-title">{{ ev.clazzName }}</div>
<div class="ev-teacher" v-if="ev.teacherName">{{ ev.teacherName }}</div>
<div class="ev-students" v-if="ev.studentNames.length">{{ ev.studentNames.join('') }}</div>
<div class="ev-meta">{{ slot.label }} {{ fmtTime(ev.startAt) }}-{{ fmtTime(ev.endAt) }}</div>
<div class="ev-teacher">{{ ev.teacherName || '未设置老师' }}</div>
<div class="ev-students">{{ ev.studentNames.length ? ev.studentNames.join('、') : '未设置学生' }}</div>
</div>
</template>
<div v-else class="cell-empty" @click="onCellClick(day.dateKey, slot.key)">+</div>
@@ -69,18 +74,16 @@ interface WeekDay {
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const timeSlotsData: TimeSlot[] = DEFAULT_TIME_SLOTS.map((t) => ({
key: t,
label: SLOT_LABELS[t] || t,
time: t,
}));
function isSameDay(a: Date, b: Date): boolean {
return a.getFullYear() === b.getFullYear()
&& a.getMonth() === b.getMonth()
&& a.getDate() === b.getDate();
}
function fmtTime(d: Date): string {
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
export default defineComponent({
name: 'ClazzMatrixView',
props: {
@@ -90,7 +93,6 @@ export default defineComponent({
},
emits: ['cell-click', 'event-click'],
setup(props, { emit }) {
const timeSlots = timeSlotsData;
const today = computed(() => new Date());
const weekDays = computed<WeekDay[]>(() => {
@@ -109,6 +111,26 @@ export default defineComponent({
return days;
});
/** 从排课数据动态计算有时段的列 */
const timeSlots = computed<TimeSlot[]>(() => {
const keys = new Set<string>();
for (const ev of props.events) {
if (!ev.visible) continue;
keys.add(ev.timeSlotKey);
}
const sorted = Array.from(keys).sort();
return sorted.map((key) => ({
key,
label: SLOT_LABELS[key] || key,
time: key,
}));
});
/** CSS Grid 列模板:左侧日期 + 每时段列至少 1.8rem */
const gridStyle = computed(() => ({
gridTemplateColumns: `1.2rem repeat(${timeSlots.value.length}, minmax(1.8rem, 1fr))`,
}));
const eventMap = computed(() => {
const map: Record<string, Record<string, NormalizedClazzEvent[]>> = {};
for (const ev of props.events) {
@@ -135,7 +157,7 @@ export default defineComponent({
emit('event-click', ev);
}
return { timeSlots, weekDays, eventMap, eventStyle, onCellClick, onEventClick };
return { timeSlots, gridStyle, weekDays, eventMap, fmtTime, eventStyle, onCellClick, onEventClick };
},
});
</script>
@@ -144,14 +166,26 @@ export default defineComponent({
.matrix-wrapper {
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
}
.matrix-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 0.28rem;
color: #999;
}
.matrix-grid {
display: grid;
grid-template-columns: 1.2rem repeat(4, 1fr);
grid-template-rows: auto repeat(7, 1fr);
/* gridTemplateColumns set dynamically via inline style */
height: 100%;
min-height: 100%;
min-width: 100%;
}
.grid-cell {
@@ -240,6 +274,15 @@ export default defineComponent({
text-overflow: ellipsis;
}
.ev-meta {
font-size: 0.16rem;
color: #888;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ev-teacher {
font-size: 0.16rem;
color: #555;
+6
View File
@@ -4,6 +4,9 @@ export const DEFAULT_TIME_SLOTS = [
'10:10',
'14:00',
'15:30',
'17:00',
'18:30',
'20:00',
];
/** 时段显示名称映射 */
@@ -12,6 +15,9 @@ export const SLOT_LABELS: Record<string, string> = {
'10:10': '第二节',
'14:00': '第三节',
'15:30': '第四节',
'17:00': '第五节',
'18:30': '第六节',
'20:00': '第七节',
};
/** 视图模式 */