chore: OXC Formatter
This commit is contained in:
@@ -3,13 +3,24 @@
|
||||
<template #main>
|
||||
<!-- 頁面標題:刻意縮小,把版面讓給下方的組別 -->
|
||||
<div class="d-flex align-center mb-3">
|
||||
<v-icon class="mr-2" color="primary" icon="mdi-calendar-week" size="18" />
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
icon="mdi-calendar-week"
|
||||
size="18"
|
||||
/>
|
||||
|
||||
<span class="text-title-small font-weight-bold text-grey-darken-1">課程建議</span>
|
||||
<span class="text-title-small font-weight-bold text-grey-darken-1"
|
||||
>課程建議</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 體適能組別:本頁所有內容都依它而定,因此獨立成一張顯眼的卡片 -->
|
||||
<v-card class="rounded-xl mb-6 overflow-hidden" elevation="1" variant="flat">
|
||||
<v-card
|
||||
class="rounded-xl mb-6 overflow-hidden"
|
||||
elevation="1"
|
||||
variant="flat"
|
||||
>
|
||||
<v-card-item class="pa-4" :class="`bg-${store.groupMeta.color}`">
|
||||
<template #prepend>
|
||||
<v-avatar class="group-avatar" size="60">
|
||||
@@ -22,7 +33,9 @@
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<span class="text-h5 font-weight-black text-white">{{ store.groupMeta.label }}</span>
|
||||
<span class="text-h5 font-weight-black text-white">{{
|
||||
store.groupMeta.label
|
||||
}}</span>
|
||||
</template>
|
||||
</v-card-item>
|
||||
|
||||
@@ -61,7 +74,12 @@
|
||||
|
||||
<!-- 載入中 -->
|
||||
<template v-if="store.loading">
|
||||
<v-skeleton-loader v-for="n in 3" :key="n" class="rounded-xl mb-4" type="article" />
|
||||
<v-skeleton-loader
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
class="rounded-xl mb-4"
|
||||
type="article"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 載入失敗 -->
|
||||
@@ -75,7 +93,9 @@
|
||||
{{ store.error }}
|
||||
|
||||
<template #append>
|
||||
<v-btn size="small" variant="text" @click="store.loadAll()">重試</v-btn>
|
||||
<v-btn size="small" variant="text" @click="store.loadAll()"
|
||||
>重試</v-btn
|
||||
>
|
||||
</template>
|
||||
</v-alert>
|
||||
|
||||
@@ -94,8 +114,15 @@
|
||||
<template v-else>
|
||||
<!-- 課程說明:對應 doc/課程說明.md -->
|
||||
<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-book-open-variant" size="20" />
|
||||
<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-book-open-variant"
|
||||
size="20"
|
||||
/>
|
||||
課程說明
|
||||
</v-card-title>
|
||||
|
||||
@@ -103,7 +130,9 @@
|
||||
|
||||
<v-expansion-panels v-model="openPanels" multiple variant="accordion">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title class="font-weight-bold">族群特徵</v-expansion-panel-title>
|
||||
<v-expansion-panel-title class="font-weight-bold"
|
||||
>族群特徵</v-expansion-panel-title
|
||||
>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<p class="text-body-medium text-grey-darken-2 mb-3">
|
||||
@@ -115,7 +144,11 @@
|
||||
:key="index"
|
||||
class="d-flex align-start ga-3 mb-2"
|
||||
>
|
||||
<v-icon color="secondary" :icon="`mdi-numeric-${index + 1}-circle`" size="20" />
|
||||
<v-icon
|
||||
color="secondary"
|
||||
:icon="`mdi-numeric-${index + 1}-circle`"
|
||||
size="20"
|
||||
/>
|
||||
<div class="text-body-medium">{{ item }}</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
@@ -132,12 +165,18 @@
|
||||
:key="index"
|
||||
class="d-flex align-start ga-3 mb-4"
|
||||
>
|
||||
<v-icon color="secondary" :icon="`mdi-numeric-${index + 1}-circle`" size="20" />
|
||||
<v-icon
|
||||
color="secondary"
|
||||
:icon="`mdi-numeric-${index + 1}-circle`"
|
||||
size="20"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ item.title }}</div>
|
||||
|
||||
<div class="text-body-medium text-grey-darken-2 mt-1">{{ item.detail }}</div>
|
||||
<div class="text-body-medium text-grey-darken-2 mt-1">
|
||||
{{ item.detail }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
@@ -154,7 +193,11 @@
|
||||
:key="index"
|
||||
class="d-flex align-start ga-3 mb-2"
|
||||
>
|
||||
<v-icon color="secondary" :icon="`mdi-numeric-${index + 1}-circle`" size="20" />
|
||||
<v-icon
|
||||
color="secondary"
|
||||
:icon="`mdi-numeric-${index + 1}-circle`"
|
||||
size="20"
|
||||
/>
|
||||
<div class="text-body-medium">{{ item }}</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
@@ -164,12 +207,21 @@
|
||||
|
||||
<!-- 運動時間建議:對應 doc/運動時間建議.png -->
|
||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
||||
<v-expansion-panels v-model="durationTablePanel" flat variant="accordion">
|
||||
<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"
|
||||
>
|
||||
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
icon="mdi-timer-outline"
|
||||
size="20"
|
||||
/>
|
||||
運動時間建議
|
||||
</v-expansion-panel-title>
|
||||
|
||||
@@ -179,9 +231,15 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -191,14 +249,18 @@
|
||||
:key="row.day"
|
||||
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
|
||||
>
|
||||
<td class="day-col text-center font-weight-bold text-primary">
|
||||
<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="text-body-medium text-wrap">
|
||||
{{ row.content }}
|
||||
</td>
|
||||
|
||||
<td class="time-col text-center text-body-medium">
|
||||
{{ row.minutes === null ? '—' : `${row.minutes} 分` }}
|
||||
{{ row.minutes === null ? "—" : `${row.minutes} 分` }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -209,12 +271,21 @@
|
||||
</v-card>
|
||||
|
||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
||||
<v-expansion-panels v-model="scheduleTablePanel" flat variant="accordion">
|
||||
<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"
|
||||
>
|
||||
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
icon="mdi-timer-outline"
|
||||
size="20"
|
||||
/>
|
||||
運動課表
|
||||
</v-expansion-panel-title>
|
||||
|
||||
@@ -224,7 +295,9 @@
|
||||
<v-table class="course-table" density="comfortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="day-col bg-primary text-white text-center">星期</th>
|
||||
<th class="day-col bg-primary text-white text-center">
|
||||
星期
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="slot in visibleSlots"
|
||||
@@ -244,7 +317,9 @@
|
||||
:key="row.day"
|
||||
:class="{ 'bg-teal-lighten-5': row.day === todayDay }"
|
||||
>
|
||||
<td class="day-col text-center font-weight-bold text-primary">
|
||||
<td
|
||||
class="day-col text-center font-weight-bold text-primary"
|
||||
>
|
||||
{{ DAY_NAMES[row.day - 1] }}
|
||||
</td>
|
||||
|
||||
@@ -253,8 +328,13 @@
|
||||
: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">
|
||||
<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)"
|
||||
@@ -273,7 +353,10 @@
|
||||
</v-card>
|
||||
|
||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
||||
<v-img :alt="`${store.groupMeta.label}課表示意圖`" :src="groupIllustration" />
|
||||
<v-img
|
||||
:alt="`${store.groupMeta.label}課表示意圖`"
|
||||
:src="groupIllustration"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
</template>
|
||||
@@ -281,102 +364,124 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FitnessGroup, TimeSlot, YesNo } from '@/api/types'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import highIllustration from '@/assets/course/high.png'
|
||||
import lowIllustration from '@/assets/course/low.png'
|
||||
import mediumIllustration from '@/assets/course/medium.png'
|
||||
import Layout from '@/components/layout/DefaultLayout.vue'
|
||||
import { COURSE_DETAIL } from '@/data/courseDetail'
|
||||
import { useCourseStore } from '@/stores/course'
|
||||
import type { FitnessGroup, TimeSlot, YesNo } from "@/api/types";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import highIllustration from "@/assets/course/high.png";
|
||||
import lowIllustration from "@/assets/course/low.png";
|
||||
import mediumIllustration from "@/assets/course/medium.png";
|
||||
import Layout from "@/components/layout/DefaultLayout.vue";
|
||||
import { COURSE_DETAIL } from "@/data/courseDetail";
|
||||
import { useCourseStore } from "@/stores/course";
|
||||
|
||||
const store = useCourseStore()
|
||||
const store = useCourseStore();
|
||||
|
||||
// 運動時間建議與課程說明的靜態內容,依組別對應
|
||||
const detail = computed(() => COURSE_DETAIL[store.group])
|
||||
const detail = computed(() => COURSE_DETAIL[store.group]);
|
||||
|
||||
// 課程說明預設只展開第一個面板(族群特徵)
|
||||
const openPanels = ref([0])
|
||||
const openPanels = ref([0]);
|
||||
|
||||
// 兩張表格預設展開,使用者可分別收合以減少頁面長度。
|
||||
const durationTablePanel = ref<string | null>('duration')
|
||||
const scheduleTablePanel = ref<string | null>('schedule')
|
||||
const durationTablePanel = ref<string | null>("duration");
|
||||
const scheduleTablePanel = ref<string | null>("schedule");
|
||||
|
||||
const GROUP_ILLUSTRATIONS: Record<FitnessGroup, string> = {
|
||||
low: lowIllustration,
|
||||
medium: mediumIllustration,
|
||||
high: highIllustration,
|
||||
}
|
||||
};
|
||||
|
||||
const groupIllustration = computed(() => GROUP_ILLUSTRATIONS[store.group])
|
||||
const groupIllustration = computed(() => GROUP_ILLUSTRATIONS[store.group]);
|
||||
|
||||
// 時段篩選功能尚未開放,UI 先隱藏;之後要開放時把這個常數改回 true 即可
|
||||
const SHOW_SLOT_FILTER = false
|
||||
const SHOW_SLOT_FILTER = false;
|
||||
|
||||
const DAY_NAMES = ['一', '二', '三', '四', '五', '六', '日']
|
||||
const DAY_NAMES = ["一", "二", "三", "四", "五", "六", "日"];
|
||||
|
||||
// 課表小卡:檔名為「星期-時段」,逐一匯入 21 張圖並依 day/slot 查表取用
|
||||
const scheduleCardModules = import.meta.glob<{ default: string }>(
|
||||
'../assets/course/schedule/*.png',
|
||||
"../assets/course/schedule/*.png",
|
||||
{ eager: true },
|
||||
)
|
||||
);
|
||||
|
||||
const SCHEDULE_CARD_DAY_KEYS = [
|
||||
'monday',
|
||||
'tuesday',
|
||||
'wednesday',
|
||||
'thursday',
|
||||
'friday',
|
||||
'saturday',
|
||||
'sunday',
|
||||
]
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday",
|
||||
];
|
||||
const SCHEDULE_CARD_SLOT_KEYS: Record<TimeSlot, string> = {
|
||||
morning: 'morning',
|
||||
noon: 'noon',
|
||||
night: 'evening',
|
||||
}
|
||||
morning: "morning",
|
||||
noon: "noon",
|
||||
night: "evening",
|
||||
};
|
||||
|
||||
const SCHEDULE_CARDS: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(scheduleCardModules).map(([path, module]) => [
|
||||
path.replace(/^.*\/|\.png$/g, ''),
|
||||
path.replace(/^.*\/|\.png$/g, ""),
|
||||
module.default,
|
||||
]),
|
||||
)
|
||||
);
|
||||
|
||||
function scheduleCardImage (day: number, slot: TimeSlot): string {
|
||||
const key = `${SCHEDULE_CARD_DAY_KEYS[day - 1]}-${SCHEDULE_CARD_SLOT_KEYS[slot]}`
|
||||
return SCHEDULE_CARDS[key]
|
||||
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 }[] = [
|
||||
{ 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 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(() => {
|
||||
const jsDay = new Date().getDay()
|
||||
return jsDay === 0 ? 7 : jsDay
|
||||
})
|
||||
const jsDay = new Date().getDay();
|
||||
return jsDay === 0 ? 7 : jsDay;
|
||||
});
|
||||
|
||||
// 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)
|
||||
const next: YesNo = value.includes(slot.key) ? "Y" : "N";
|
||||
store.setSlot(slot.key, next);
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
store.loadAll()
|
||||
})
|
||||
store.loadAll();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user