feat: 課程建議
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<Layout>
|
||||
<template #main>
|
||||
<!-- 標題卡:組別與說明 -->
|
||||
<v-card class="border-primary border-xl rounded-xl mb-6" variant="flat">
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2" color="primary" icon="mdi-calendar-week" />
|
||||
課程建議
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-chip :color="store.groupMeta.color" size="small" variant="flat">
|
||||
{{ store.groupMeta.label }}
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="text-wrap">
|
||||
{{ store.groupMeta.description }}
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pt-4">
|
||||
<div class="text-title-small text-grey mb-2">選擇要顯示的時段</div>
|
||||
|
||||
<v-btn-toggle
|
||||
v-model="selectedSlots"
|
||||
border
|
||||
class="d-flex"
|
||||
color="primary"
|
||||
divided
|
||||
multiple
|
||||
variant="flat"
|
||||
>
|
||||
<v-btn
|
||||
v-for="slot in SLOTS"
|
||||
:key="slot.key"
|
||||
class="flex-grow-1"
|
||||
:prepend-icon="slot.icon"
|
||||
size="small"
|
||||
:value="slot.key"
|
||||
>
|
||||
{{ slot.label }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 載入中 -->
|
||||
<template v-if="store.loading">
|
||||
<v-skeleton-loader
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
class="rounded-xl mb-4"
|
||||
type="article"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 載入失敗 -->
|
||||
<v-alert
|
||||
v-else-if="store.error"
|
||||
class="rounded-xl"
|
||||
icon="mdi-alert-circle-outline"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ store.error }}
|
||||
|
||||
<template #append>
|
||||
<v-btn size="small" variant="text" @click="store.loadAll()">重試</v-btn>
|
||||
</template>
|
||||
</v-alert>
|
||||
|
||||
<!-- 沒有選取任何時段 -->
|
||||
<v-alert
|
||||
v-else-if="!store.hasCourse"
|
||||
class="rounded-xl"
|
||||
icon="mdi-calendar-remove-outline"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
>
|
||||
目前沒有可顯示的課程,請至少選擇一個時段。
|
||||
</v-alert>
|
||||
|
||||
<!-- 一週課表:仿 doc/課程表示意 的表格版型,星期為列、時段為欄 -->
|
||||
<template v-else>
|
||||
<v-card class="rounded-xl elevation-1 overflow-hidden">
|
||||
<v-table class="course-table" density="comfortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="day-col bg-secondary text-white text-center">星期</th>
|
||||
|
||||
<th
|
||||
v-for="slot in visibleSlots"
|
||||
:key="slot.key"
|
||||
class="text-white text-center"
|
||||
:class="slot.headerClass"
|
||||
>
|
||||
<v-icon class="mr-1" :icon="slot.icon" size="16" />
|
||||
{{ slot.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="row in store.courseList" :key="row.day">
|
||||
<td class="day-col text-center font-weight-bold text-primary">
|
||||
{{ DAY_NAMES[row.day - 1] }}
|
||||
</td>
|
||||
|
||||
<td
|
||||
v-for="slot in visibleSlots"
|
||||
:key="slot.key"
|
||||
class="text-body-medium text-wrap"
|
||||
>
|
||||
{{ row[slot.key] }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</template>
|
||||
</template>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TimeSlot, YesNo } from '@/api/types'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import Layout from '@/components/layout/DefaultLayout.vue'
|
||||
import { useCourseStore } from '@/stores/course'
|
||||
|
||||
const store = useCourseStore()
|
||||
|
||||
const DAY_NAMES = ['一', '二', '三', '四', '五', '六', '日']
|
||||
|
||||
const SLOTS: { key: TimeSlot, label: string, icon: string, headerClass: string }[] = [
|
||||
{ key: 'morning', label: '早上', icon: 'mdi-weather-sunset-up', headerClass: 'bg-deep-orange' },
|
||||
{ key: 'noon', label: '中午', icon: 'mdi-white-balance-sunny', headerClass: 'bg-amber-darken-2' },
|
||||
{ key: 'night', label: '晚上', icon: 'mdi-weather-night', headerClass: 'bg-deep-purple' },
|
||||
]
|
||||
|
||||
// 依目前選取的時段決定表格要顯示哪幾欄,順序固定為早上/中午/晚上
|
||||
const visibleSlots = computed(() => SLOTS.filter(slot => store.selectedSlots.includes(slot.key)))
|
||||
|
||||
// v-btn-toggle 以陣列表達勾選狀態,store 則以 Y/N 保存,兩者在此橋接
|
||||
const selectedSlots = computed<TimeSlot[]>({
|
||||
get: () => store.selectedSlots,
|
||||
set: value => {
|
||||
for (const slot of SLOTS) {
|
||||
const next: YesNo = value.includes(slot.key) ? 'Y' : 'N'
|
||||
store.setSlot(slot.key, next)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
store.loadAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Vuetify 的 v-table 沒有「欄位依內容自動收窄/固定星期欄寬」的內建設定,
|
||||
需要 table-layout: fixed 才能讓中文內容改為換行而非撐開表格。 */
|
||||
.course-table :deep(table) {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.day-col {
|
||||
width: 15%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user