实验课表
This commit is contained in:
+131
-16
@@ -69,6 +69,10 @@ const planStatusLabels: Record<string, string> = {
|
||||
Published: '已发布',
|
||||
Archived: '已归档',
|
||||
}
|
||||
const experimentModeLabels: Record<string, string> = {
|
||||
Centralized: '集中安排',
|
||||
SelfScheduled: '自主预约',
|
||||
}
|
||||
|
||||
const grades = computed(() =>
|
||||
[...new Set(classes.value.map((item) => item.grade))].sort((a, b) => b - a),
|
||||
@@ -128,9 +132,27 @@ const examEntries = computed(() => {
|
||||
}]
|
||||
})
|
||||
})
|
||||
const experimentEntries = computed(() => {
|
||||
if (!termMonday.value) return []
|
||||
return (timetable.value?.experimentEntries ?? []).flatMap((entry: any) => {
|
||||
const experimentDate = parseDate(entry.experimentDate)
|
||||
if (!experimentDate) return []
|
||||
const week = Math.floor(
|
||||
(experimentDate.getTime() - termMonday.value!.getTime()) / (7 * 86400000),
|
||||
) + 1
|
||||
if (week < 1) return []
|
||||
return [{
|
||||
...entry,
|
||||
startWeek: week,
|
||||
endWeek: week,
|
||||
weekPattern: 'All',
|
||||
}]
|
||||
})
|
||||
})
|
||||
const timedEntries = computed(() => [
|
||||
...(timetable.value?.entries ?? []),
|
||||
...examEntries.value,
|
||||
...experimentEntries.value,
|
||||
])
|
||||
const hasTimedEntries = computed(() => timedEntries.value.length > 0)
|
||||
const maxPeriods = computed(() => {
|
||||
@@ -174,6 +196,13 @@ const sortedExamEntries = computed(() =>
|
||||
String(a.courseCode).localeCompare(String(b.courseCode)),
|
||||
),
|
||||
)
|
||||
const sortedExperimentEntries = computed(() =>
|
||||
experimentEntries.value.slice().sort((a: any, b: any) =>
|
||||
String(a.experimentDate).localeCompare(String(b.experimentDate)) ||
|
||||
a.startPeriod - b.startPeriod ||
|
||||
String(a.experimentProjectCode).localeCompare(String(b.experimentProjectCode)),
|
||||
),
|
||||
)
|
||||
const visibleGridEntries = computed(() =>
|
||||
viewMode.value === 'overview'
|
||||
? (timetable.value?.entries ?? [])
|
||||
@@ -215,19 +244,31 @@ function weeks(entry: any) {
|
||||
}
|
||||
|
||||
function entryKey(entry: any) {
|
||||
return `${entry.isExam ? 'exam' : 'course'}-${entry.id}-${entry.teachingTaskId ?? ''}`
|
||||
const kind = entry.isExperiment ? 'experiment' : entry.isExam ? 'exam' : 'course'
|
||||
return `${kind}-${entry.id}-${entry.teachingTaskId ?? ''}`
|
||||
}
|
||||
|
||||
function entryTiming(entry: any) {
|
||||
const periods = `第 ${entry.startPeriod}—${entry.startPeriod + entry.periodCount - 1} 节`
|
||||
if (entry.isExperiment) {
|
||||
return `${formatActivityDate(entry.experimentDate, '实验日期待定')} · ${periods}`
|
||||
}
|
||||
return entry.isExam
|
||||
? `${formatExamDate(entry.examDate)} · ${periods}`
|
||||
? `${formatActivityDate(entry.examDate, '考试日期待定')} · ${periods}`
|
||||
: `${weeks(entry)} · ${periods}`
|
||||
}
|
||||
|
||||
function formatExamDate(value?: string) {
|
||||
return formatActivityDate(value, '考试日期待定')
|
||||
}
|
||||
|
||||
function formatActivityDate(value?: string, fallback = '日期待定') {
|
||||
const date = parseDate(value)
|
||||
return date ? `${date.getMonth() + 1}月${date.getDate()}日` : '考试日期待定'
|
||||
return date ? `${date.getMonth() + 1}月${date.getDate()}日` : fallback
|
||||
}
|
||||
|
||||
function experimentMode(entry: any) {
|
||||
return experimentModeLabels[entry.experimentArrangementMode] ?? '实验安排'
|
||||
}
|
||||
|
||||
function location(entry: any) {
|
||||
@@ -890,9 +931,14 @@ onMounted(async () => {
|
||||
<template v-if="examEntries.length">
|
||||
· 已融入 {{ examEntries.length }} 场考试
|
||||
</template>
|
||||
<template v-if="experimentEntries.length">
|
||||
· 已融入 {{ experimentEntries.length }} 场实验
|
||||
</template>
|
||||
</span>
|
||||
<span v-else-if="examEntries.length">
|
||||
固定课表尚未发布 · 已载入 {{ examEntries.length }} 场考试
|
||||
<span v-else-if="examEntries.length || experimentEntries.length">
|
||||
固定课表尚未发布
|
||||
<template v-if="examEntries.length"> · 已载入 {{ examEntries.length }} 场考试</template>
|
||||
<template v-if="experimentEntries.length"> · 已载入 {{ experimentEntries.length }} 场实验</template>
|
||||
</span>
|
||||
<span v-else>本学期课表尚未发布</span>
|
||||
</div>
|
||||
@@ -920,7 +966,9 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<div
|
||||
v-if="(viewMode === 'week' && hasTimedEntries) || (viewMode === 'overview' && hasOverviewCourseEntries)"
|
||||
v-if="(viewMode === 'week' && hasTimedEntries) ||
|
||||
(viewMode === 'overview' &&
|
||||
(hasOverviewCourseEntries || sortedExamEntries.length || sortedExperimentEntries.length))"
|
||||
class="timetable-view-section"
|
||||
>
|
||||
<div class="view-context">
|
||||
@@ -928,12 +976,12 @@ onMounted(async () => {
|
||||
<strong>{{ viewMode === 'overview' ? '全学期总览' : selectedWeekLabel }}</strong>
|
||||
<span>
|
||||
{{ viewMode === 'overview'
|
||||
? '固定课程按节次展示,考试安排按日期列于下方'
|
||||
: `本周共 ${weekEntries.length} 项课程、考试安排` }}
|
||||
? '固定课程按节次展示,考试与实验按日期列于下方'
|
||||
: `本周共 ${weekEntries.length} 项课程、考试与实验安排` }}
|
||||
</span>
|
||||
</div>
|
||||
<small v-if="viewMode === 'overview'">课程卡片保留起止周与单双周信息</small>
|
||||
<small v-else-if="!weekEntries.length">本周没有课程或考试安排</small>
|
||||
<small v-else-if="!weekEntries.length">本周没有课程、考试或实验安排</small>
|
||||
</div>
|
||||
<div class="timetable-scroll">
|
||||
<div
|
||||
@@ -963,14 +1011,21 @@ onMounted(async () => {
|
||||
v-for="entry in visibleGridEntries"
|
||||
:key="entryKey(entry)"
|
||||
class="course-block"
|
||||
:class="{ 'exam-block': entry.isExam }"
|
||||
:class="{
|
||||
'exam-block': entry.isExam,
|
||||
'experiment-block': entry.isExperiment,
|
||||
}"
|
||||
:style="gridEntryStyle(entry)"
|
||||
>
|
||||
<strong>
|
||||
<span v-if="entry.isExam" class="entry-kind">考试</span>
|
||||
{{ entry.courseName }}
|
||||
<span v-if="entry.isExperiment" class="entry-kind experiment-kind">实验</span>
|
||||
{{ entry.isExperiment ? entry.experimentProjectName : entry.courseName }}
|
||||
</strong>
|
||||
<span v-if="entry.isExam && entry.examPlanName">{{ entry.examPlanName }}</span>
|
||||
<span v-if="entry.isExperiment">
|
||||
{{ entry.courseName }} · {{ experimentMode(entry) }}
|
||||
</span>
|
||||
<span>{{ entry.teacherNames.join('、') || '教师待定' }}</span>
|
||||
<span>{{ location(entry) }}</span>
|
||||
<small>{{ entryTiming(entry) }}</small>
|
||||
@@ -1006,24 +1061,33 @@ onMounted(async () => {
|
||||
v-for="entry in dayEntries"
|
||||
:key="entryKey(entry)"
|
||||
class="day-course-block"
|
||||
:class="{ 'exam-block': entry.isExam }"
|
||||
:class="{
|
||||
'exam-block': entry.isExam,
|
||||
'experiment-block': entry.isExperiment,
|
||||
}"
|
||||
:style="dayEntryStyle(entry)"
|
||||
>
|
||||
<strong>
|
||||
<span v-if="entry.isExam" class="entry-kind">考试</span>
|
||||
{{ entry.courseName }}
|
||||
<span v-if="entry.isExperiment" class="entry-kind experiment-kind">实验</span>
|
||||
{{ entry.isExperiment ? entry.experimentProjectName : entry.courseName }}
|
||||
</strong>
|
||||
<span v-if="entry.isExam && entry.examPlanName">{{ entry.examPlanName }}</span>
|
||||
<span v-if="entry.isExperiment">
|
||||
{{ entry.courseName }} · {{ experimentMode(entry) }}
|
||||
</span>
|
||||
<span>{{ entry.teacherNames.join('、') || '教师待定' }}</span>
|
||||
<span>{{ location(entry) }}</span>
|
||||
<small>{{ entryTiming(entry) }}</small>
|
||||
</article>
|
||||
<span v-if="!dayEntries.length" class="day-no-courses">当日无课程安排</span>
|
||||
<span v-if="!dayEntries.length" class="day-no-courses">当日无教学安排</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty
|
||||
v-else-if="timetable && !loading && !hasTimedEntries && !timetable.flexibleCourses?.length"
|
||||
:description="timetable.plan ? '该课表暂时没有课程或考试安排' : '所选学期尚未发布课表或考试安排'"
|
||||
:description="timetable.plan
|
||||
? '该课表暂时没有课程、考试或实验安排'
|
||||
: '所选学期尚未发布课表、考试或实验安排'"
|
||||
/>
|
||||
|
||||
<section v-if="viewMode === 'overview' && sortedExamEntries.length" class="exam-overview">
|
||||
@@ -1054,6 +1118,38 @@ onMounted(async () => {
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="viewMode === 'overview' && sortedExperimentEntries.length"
|
||||
class="exam-overview experiment-overview"
|
||||
>
|
||||
<header>
|
||||
<div>
|
||||
<span>LAB SESSIONS</span>
|
||||
<strong>个人实验安排</strong>
|
||||
</div>
|
||||
<small>共 {{ sortedExperimentEntries.length }} 场 · 预约变化会自动同步</small>
|
||||
</header>
|
||||
<div class="exam-overview-list">
|
||||
<article v-for="entry in sortedExperimentEntries" :key="entryKey(entry)">
|
||||
<div class="exam-date">
|
||||
<strong>{{ formatActivityDate(entry.experimentDate, '日期待定') }}</strong>
|
||||
<span>{{ weekdays[entry.dayOfWeek] }}</span>
|
||||
<small>第 {{ entry.startWeek }} 周</small>
|
||||
</div>
|
||||
<div class="exam-summary">
|
||||
<span>{{ entry.experimentProjectCode }} · {{ experimentMode(entry) }}</span>
|
||||
<strong>{{ entry.experimentProjectName }}</strong>
|
||||
<p>{{ entry.courseName }} · {{ location(entry) }}</p>
|
||||
<small>
|
||||
第 {{ entry.startWeek }} 周 ·
|
||||
第 {{ entry.startPeriod }}—{{ entry.startPeriod + entry.periodCount - 1 }} 节
|
||||
</small>
|
||||
<small>{{ entry.teacherNames.join('、') || '实验教师待定' }}</small>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1069,7 +1165,7 @@ onMounted(async () => {
|
||||
<span>LIVE FEED</span>
|
||||
<div>
|
||||
<strong>订阅已启用</strong>
|
||||
<small>课表和考试安排变更后,日历客户端会在下次同步时自动更新。</small>
|
||||
<small>课表、考试和实验安排变更后,日历客户端会在下次同步时自动更新。</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1132,6 +1228,7 @@ onMounted(async () => {
|
||||
<ul>
|
||||
<li>已发布课表中的固定课程和调停课后的最新安排</li>
|
||||
<li>学生考试、补考;教师授课、监考和补考监考</li>
|
||||
<li>集中实验及学生本人已预约的自主实验场次</li>
|
||||
<li>非排时课程会在学期首日生成全天提醒</li>
|
||||
</ul>
|
||||
<p>苹果日历可直接点击订阅;Google 日历、Outlook 可复制地址后选择“通过 URL 添加”。服务需具备外网可访问的 HTTPS 地址才能跨设备同步。</p>
|
||||
@@ -1227,7 +1324,11 @@ onMounted(async () => {
|
||||
.course-block.exam-block, .day-course-block.exam-block { border-left-color: #b65b32; background: #fff1e8; color: #70442f; }
|
||||
.course-block.exam-block strong, .day-course-block.exam-block strong { color: #78391e; }
|
||||
.course-block.exam-block small, .day-course-block.exam-block small { color: #8b5b43; }
|
||||
.course-block.experiment-block, .day-course-block.experiment-block { border-left-color: #168276; background: #e7f5f1; color: #315f59; }
|
||||
.course-block.experiment-block strong, .day-course-block.experiment-block strong { color: #155b53; }
|
||||
.course-block.experiment-block small, .day-course-block.experiment-block small { color: #527a74; }
|
||||
.entry-kind { display: inline-block; margin-right: 5px; padding: 1px 5px; border-radius: 2px; background: #b65b32; color: #fff; font-size: 10px !important; line-height: 1.5; vertical-align: 1px; }
|
||||
.entry-kind.experiment-kind { background: #168276; }
|
||||
.exam-overview { margin-top: 20px; border: 1px solid #e2d6cf; background: #fffaf7; }
|
||||
.exam-overview > header { padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid #eaded7; background: #fff5ef; }
|
||||
.exam-overview > header > div { display: flex; align-items: baseline; gap: 10px; }
|
||||
@@ -1245,6 +1346,20 @@ onMounted(async () => {
|
||||
.exam-summary > strong { color: #533225; font-size: 15px; }
|
||||
.exam-summary p { margin: 0; color: #6e584e; font-size: 12px; }
|
||||
.exam-summary small { color: #8a7469; font-size: 11px; line-height: 1.5; }
|
||||
.experiment-overview { border-color: #cfe4df; background: #f8fcfb; }
|
||||
.experiment-overview > header { border-bottom-color: #d8ebe7; background: #eff9f6; }
|
||||
.experiment-overview > header span { color: #168276; }
|
||||
.experiment-overview > header strong { color: #174f49; }
|
||||
.experiment-overview > header small { color: #63827d; }
|
||||
.experiment-overview .exam-overview-list article { border-color: #d5e8e4; border-left-color: #168276; }
|
||||
.experiment-overview .exam-date { border-right-color: #dbece8; background: #f0faf7; }
|
||||
.experiment-overview .exam-date strong { color: #176b62; }
|
||||
.experiment-overview .exam-date span { color: #4c706b; }
|
||||
.experiment-overview .exam-date small { color: #718d88; }
|
||||
.experiment-overview .exam-summary > span { color: #168276; }
|
||||
.experiment-overview .exam-summary > strong { color: #174f49; }
|
||||
.experiment-overview .exam-summary p { color: #506f6a; }
|
||||
.experiment-overview .exam-summary small { color: #6c8581; }
|
||||
.day-view { border: 1px solid #dce4eb; }
|
||||
.day-view > header { padding: 14px 16px; display: flex; align-items: baseline; justify-content: space-between; background: #173e72; color: #fff; }
|
||||
.day-view > header > div { display: grid; gap: 3px; }
|
||||
|
||||
Reference in New Issue
Block a user