style: course

This commit is contained in:
skytek_xinliang
2026-08-13 11:49:28 +08:00
parent 3839f8f4cc
commit 6000639b1c
2 changed files with 121 additions and 89 deletions
+112 -80
View File
@@ -164,89 +164,112 @@
<!-- 運動時間建議對應 doc/運動時間建議.png -->
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
<v-card-title class="text-title-medium font-weight-bold text-primary d-flex align-center">
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
運動時間建議
</v-card-title>
<v-divider />
<v-table class="course-table" density="comfortable">
<thead>
<tr>
<th class="day-col bg-primary text-white text-center">星期</th>
<th class="bg-primary text-white text-center">運動內容</th>
<th class="time-col bg-primary text-white text-center">時間</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in detail.duration"
:key="row.day"
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
<v-expansion-panels v-model="durationTablePanel" flat variant="accordion">
<v-expansion-panel value="duration">
<v-expansion-panel-title
class="course-table-panel-title text-title-medium font-weight-bold text-primary"
>
<td class="day-col text-center font-weight-bold text-primary">
{{ DAY_NAMES[row.day - 1] }}
</td>
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
運動時間建議
</v-expansion-panel-title>
<td class="text-body-medium text-wrap">{{ row.content }}</td>
<v-divider />
<td class="time-col text-center text-body-medium">
{{ row.minutes === null ? '—' : `${row.minutes}` }}
</td>
</tr>
</tbody>
</v-table>
<v-expansion-panel-text class="pa-0">
<v-table class="course-table" density="comfortable">
<thead>
<tr>
<th class="day-col bg-primary text-white text-center">星期</th>
<th class="bg-primary text-white text-center">運動內容</th>
<th class="time-col bg-primary text-white text-center">時間</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in detail.duration"
:key="row.day"
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
>
<td class="day-col text-center font-weight-bold text-primary">
{{ DAY_NAMES[row.day - 1] }}
</td>
<td class="text-body-medium text-wrap">{{ row.content }}</td>
<td class="time-col text-center text-body-medium">
{{ row.minutes === null ? '—' : `${row.minutes}` }}
</td>
</tr>
</tbody>
</v-table>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
<v-table class="course-table" density="comfortable">
<thead>
<tr>
<th class="day-col bg-primary 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"
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
<v-expansion-panels v-model="scheduleTablePanel" flat variant="accordion">
<v-expansion-panel value="schedule">
<v-expansion-panel-title
class="course-table-panel-title text-title-medium font-weight-bold text-primary"
>
<td class="day-col text-center font-weight-bold text-primary">
{{ DAY_NAMES[row.day - 1] }}
</td>
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
運動課表
</v-expansion-panel-title>
<td
v-for="slot in visibleSlots"
:key="slot.key"
class="slot-cell text-body-medium text-wrap"
>
<div class="d-flex flex-column flex-md-row align-center ga-1 ga-md-3">
<v-avatar class="slot-avatar flex-shrink-0" rounded="lg">
<v-img
:alt="`${DAY_NAMES[row.day - 1]}${slot.label}課表插圖`"
:src="scheduleCardImage(row.day, slot.key)"
/>
</v-avatar>
<v-divider />
<span>{{ row[slot.key] }}</span>
</div>
</td>
</tr>
</tbody>
</v-table>
<v-expansion-panel-text class="pa-0">
<v-table class="course-table" density="comfortable">
<thead>
<tr>
<th class="day-col bg-primary 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"
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
>
<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="slot-cell text-body-medium text-wrap"
>
<div class="d-flex flex-column flex-md-row align-center ga-1 ga-md-3">
<v-avatar class="slot-avatar flex-shrink-0" rounded="lg">
<v-img
:alt="`${DAY_NAMES[row.day - 1]}${slot.label}課表插圖`"
:src="scheduleCardImage(row.day, slot.key)"
/>
</v-avatar>
<span>{{ row[slot.key] }}</span>
</div>
</td>
</tr>
</tbody>
</v-table>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
@@ -275,6 +298,10 @@ const detail = computed(() => COURSE_DETAIL[store.group])
// 課程說明預設只展開第一個面板(族群特徵)
const openPanels = ref([0])
// 兩張表格預設展開,使用者可分別收合以減少頁面長度。
const durationTablePanel = ref<string | null>('duration')
const scheduleTablePanel = ref<string | null>('schedule')
const GROUP_ILLUSTRATIONS: Record<FitnessGroup, string> = {
low: lowIllustration,
medium: mediumIllustration,
@@ -291,7 +318,7 @@ const DAY_NAMES = ['一', '二', '三', '四', '五', '六', '日']
// 課表小卡:檔名為「星期-時段」,逐一匯入 21 張圖並依 day/slot 查表取用
const scheduleCardModules = import.meta.glob<{ default: string }>(
'../assets/course/schedule/*.png',
{ eager: true }
{ eager: true },
)
const SCHEDULE_CARD_DAY_KEYS = [
@@ -313,22 +340,22 @@ const SCHEDULE_CARDS: Record<string, string> = Object.fromEntries(
Object.entries(scheduleCardModules).map(([path, module]) => [
path.replace(/^.*\/|\.png$/g, ''),
module.default,
])
]),
)
function scheduleCardImage(day: number, slot: TimeSlot): string {
function scheduleCardImage (day: number, slot: TimeSlot): string {
const key = `${SCHEDULE_CARD_DAY_KEYS[day - 1]}-${SCHEDULE_CARD_SLOT_KEYS[slot]}`
return SCHEDULE_CARDS[key]
}
const SLOTS: { key: TimeSlot; label: string; icon: string; headerClass: string }[] = [
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)))
const visibleSlots = computed(() => SLOTS.filter(slot => store.selectedSlots.includes(slot.key)))
// 今天對應的星期幾,換算成與 CourseRow.day 相同的規則(1 = 週一 … 7 = 週日)
const todayDay = computed(() => {
@@ -339,7 +366,7 @@ const todayDay = computed(() => {
// v-btn-toggle 以陣列表達勾選狀態,store 則以 Y/N 保存,兩者在此橋接
const selectedSlots = computed<TimeSlot[]>({
get: () => store.selectedSlots,
set: (value) => {
set: value => {
for (const slot of SLOTS) {
const next: YesNo = value.includes(slot.key) ? 'Y' : 'N'
store.setSlot(slot.key, next)
@@ -359,6 +386,11 @@ onMounted(() => {
table-layout: fixed;
}
.course-table-panel-title {
min-height: 40px;
padding: 8px 16px;
}
.day-col {
width: 15%;
}
+9 -9
View File
@@ -14,17 +14,17 @@ import 'vuetify/styles'
const HPTheme: ThemeDefinition = {
dark: false,
colors: {
background: '#F0F9F6', // 頁面背景
surface: '#FFFFFF', // 卡片與白底區塊
primary: '#1D7063', // 主要品牌色
'background': '#F0F9F6', // 頁面背景
'surface': '#FFFFFF', // 卡片與白底區塊
'primary': '#1D7063', // 主要品牌色
'primary-darken-1': '#145248',
secondary: '#dc5349', // CTA 按鈕
'secondary': '#dc5349', // CTA 按鈕
'secondary-darken-1': '#ab4c3d',
accent: '#47C2A6', // 標籤與強調
error: '#B00020',
info: '#2196F3',
success: '#4CAF50',
warning: '#FB8C00',
'accent': '#47C2A6', // 標籤與強調
'error': '#B00020',
'info': '#2196F3',
'success': '#4CAF50',
'warning': '#FB8C00',
},
}