From 29262b47c96452e2532ba411f3aba6b16ab836d2 Mon Sep 17 00:00:00 2001 From: YuDong Date: Wed, 9 Sep 2026 22:41:41 +0800 Subject: [PATCH 1/3] refactor(timetable): derive timeline sessions from existing matrix --- src/components/pages/main/classTable.vue | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/components/pages/main/classTable.vue b/src/components/pages/main/classTable.vue index 2fb7711..93c6a63 100644 --- a/src/components/pages/main/classTable.vue +++ b/src/components/pages/main/classTable.vue @@ -159,6 +159,7 @@ import { Course, InitTable, GetCourseTable, + WeekDayToInt } from "@functions/general"; import renderImage from "@functions/image_render.ts"; import { @@ -227,6 +228,10 @@ const classes = [ "I", "J", ]; + +const TIMELINE_START_MINUTE = 7 * 60; +const TIMELINE_SLOT_MINUTES = 30; + const className = ref(); const classRoom = ref(); const weekDay = ref("星期"); @@ -340,4 +345,70 @@ async function refresh_table() { const state = reactive({ checked: false, }); + +function timeToMinute(time) { + if (typeof time !== "string") return NaN; + + const [hour, minute] = time.split(":").map(Number); + + if (!Number.isFinite(hour) || !Number.isFinite(minute)) { + return NaN; + } + + return hour * 60 + minute; +} + + +const timelineSessions = computed(() => { + const matrix = + TotalCourseData.value?.[activeIndex.value]?.classStorage; + + if (!Array.isArray(matrix)) { + return []; + } + + const sessions = []; + + for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { + for (const weekday of week) { + const course = + matrix[rowIndex]?.[WeekDayToInt[weekday]]; + + if (!course?.getIsCourse()) { + continue; + } + + const length = course.getLength(); + + // rowspanize() 已將連續區段的長度 + // 設定在該區段第一格。 + if (!length) { + continue; + } + + const startMinute = + timeToMinute(course.getStartTime()); + + if (!Number.isFinite(startMinute)) { + continue; + } + + const endMinute = + TIMELINE_START_MINUTE + + (rowIndex + length) * + TIMELINE_SLOT_MINUTES; + + sessions.push({ + key: `${course.getUuid()}-${weekday}-${rowIndex}`, + weekday, + startMinute, + endMinute, + course, + }); + } + } + + return sessions; +}); + From 90a8afe6b9130689c4ef625eeea33b4d48239756 Mon Sep 17 00:00:00 2001 From: YuDong Date: Thu, 10 Sep 2026 01:47:08 +0800 Subject: [PATCH 2/3] feat(timetable): render sessions on time-based timeline --- src/components/pages/main/classTable.vue | 173 +++++++++++++++++++++-- 1 file changed, 159 insertions(+), 14 deletions(-) diff --git a/src/components/pages/main/classTable.vue b/src/components/pages/main/classTable.vue index 93c6a63..97921d3 100644 --- a/src/components/pages/main/classTable.vue +++ b/src/components/pages/main/classTable.vue @@ -107,7 +107,7 @@ -
+
@@ -117,14 +117,16 @@ - - - - - - + + + + +
+
+ 時間 +
+ +
+ 星期{{ weekday }} +
+
+ + +
+ + + + +
+
+ +
+ + +
+
+ + {{ formatMinute(session.startMinute) }} + – + {{ formatMinute(session.endMinute) }} + + + + {{ session.course.getCourseName() }} + + + + {{ session.course.getClassroom() }} + +
+
+
+
+
+ @@ -159,7 +255,7 @@ import { Course, InitTable, GetCourseTable, - WeekDayToInt + WeekDayToInt, } from "@functions/general"; import renderImage from "@functions/image_render.ts"; import { @@ -230,7 +326,9 @@ const classes = [ ]; const TIMELINE_START_MINUTE = 7 * 60; +const TIMELINE_END_MINUTE = 22 * 60; const TIMELINE_SLOT_MINUTES = 30; +const TIMELINE_HOUR_HEIGHT = 64; const className = ref(); const classRoom = ref(); @@ -346,6 +444,7 @@ const state = reactive({ checked: false, }); +// for "HH:mm" -> 430 function timeToMinute(time) { if (typeof time !== "string") return NaN; @@ -358,6 +457,24 @@ function timeToMinute(time) { return hour * 60 + minute; } +// for 430 -> "HH:mm" +function formatMinute(minute) { + const hour = Math.floor(minute / 60); + const minutes = minute % 60; + + return `${String(hour).padStart(2, "0")}:${String(minutes).padStart( + 2, + "0", + )}`; +} + +function minuteToTimelineOffset(minute) { + return ( + ((minute - TIMELINE_START_MINUTE) * TIMELINE_HOUR_HEIGHT) / 60 + ); +} + +const timelineHeight = minuteToTimelineOffset(TIMELINE_END_MINUTE); const timelineSessions = computed(() => { const matrix = @@ -371,8 +488,7 @@ const timelineSessions = computed(() => { for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { for (const weekday of week) { - const course = - matrix[rowIndex]?.[WeekDayToInt[weekday]]; + const course = matrix[rowIndex]?.[WeekDayToInt[weekday]]; if (!course?.getIsCourse()) { continue; @@ -386,8 +502,7 @@ const timelineSessions = computed(() => { continue; } - const startMinute = - timeToMinute(course.getStartTime()); + const startMinute = timeToMinute(course.getStartTime()); if (!Number.isFinite(startMinute)) { continue; @@ -395,8 +510,7 @@ const timelineSessions = computed(() => { const endMinute = TIMELINE_START_MINUTE + - (rowIndex + length) * - TIMELINE_SLOT_MINUTES; + (rowIndex + length) * TIMELINE_SLOT_MINUTES; sessions.push({ key: `${course.getUuid()}-${weekday}-${rowIndex}`, @@ -411,4 +525,35 @@ const timelineSessions = computed(() => { return sessions; }); +const timelineHours = Array.from( + { + length: (TIMELINE_END_MINUTE - TIMELINE_START_MINUTE) / 60 + 1, + }, + (_, index) => index + 7, +); + +const sessionsByDay = computed(() => { + const result = Object.fromEntries( + week.map((weekday) => [weekday, []]), + ); + + for (const session of timelineSessions.value) { + result[session.weekday].push(session); + } + + return result; +}); + +function sessionStyle(session) { + const top = minuteToTimelineOffset(session.startMinute); + + const bottom = minuteToTimelineOffset(session.endMinute); + + return { + top: `${top}px`, + height: `${bottom - top}px`, + backgroundColor: session.course.getColor(), + color: session.course.getTextColor(), + }; +} From 608b69e6a4afb9bc07a5e98a9188ecc7cc12332d Mon Sep 17 00:00:00 2001 From: YuDong Date: Thu, 10 Sep 2026 02:23:03 +0800 Subject: [PATCH 3/3] feat(timetable): add course detail interactions --- src/components/pages/main/classTable.vue | 191 ++++++++++++++++++++++- 1 file changed, 186 insertions(+), 5 deletions(-) diff --git a/src/components/pages/main/classTable.vue b/src/components/pages/main/classTable.vue index 97921d3..dfebb34 100644 --- a/src/components/pages/main/classTable.vue +++ b/src/components/pages/main/classTable.vue @@ -206,11 +206,13 @@ }"> -
+ type="button" + class="absolute inset-x-0.5 z-10 cursor-pointer overflow-hidden rounded-md border border-white/70 px-1 py-0.5 text-center shadow-sm transition hover:brightness-95" + :style="sessionStyle(session)" + @click="openCourseDetails(session)">
@@ -229,7 +231,7 @@ {{ session.course.getClassroom() }}
-
+ @@ -237,6 +239,110 @@ + + +
節次 星期一星期二星期三星期四星期五星期六 + 星期{{ weekday }} +