style: course
This commit is contained in:
@@ -164,89 +164,112 @@
|
|||||||
|
|
||||||
<!-- 運動時間建議:對應 doc/運動時間建議.png -->
|
<!-- 運動時間建議:對應 doc/運動時間建議.png -->
|
||||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
<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-expansion-panels v-model="durationTablePanel" flat variant="accordion">
|
||||||
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
|
<v-expansion-panel value="duration">
|
||||||
運動時間建議
|
<v-expansion-panel-title
|
||||||
</v-card-title>
|
class="course-table-panel-title text-title-medium font-weight-bold text-primary"
|
||||||
|
|
||||||
<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 }"
|
|
||||||
>
|
>
|
||||||
<td class="day-col text-center font-weight-bold text-primary">
|
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
|
||||||
{{ DAY_NAMES[row.day - 1] }}
|
運動時間建議
|
||||||
</td>
|
</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">
|
<v-expansion-panel-text class="pa-0">
|
||||||
{{ row.minutes === null ? '—' : `${row.minutes} 分` }}
|
<v-table class="course-table" density="comfortable">
|
||||||
</td>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
</tbody>
|
<th class="day-col bg-primary text-white text-center">星期</th>
|
||||||
</v-table>
|
<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>
|
||||||
|
|
||||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
||||||
<v-table class="course-table" density="comfortable">
|
<v-expansion-panels v-model="scheduleTablePanel" flat variant="accordion">
|
||||||
<thead>
|
<v-expansion-panel value="schedule">
|
||||||
<tr>
|
<v-expansion-panel-title
|
||||||
<th class="day-col bg-primary text-white text-center">星期</th>
|
class="course-table-panel-title text-title-medium font-weight-bold text-primary"
|
||||||
|
|
||||||
<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">
|
<v-icon class="mr-2" color="primary" icon="mdi-timer-outline" size="20" />
|
||||||
{{ DAY_NAMES[row.day - 1] }}
|
運動課表
|
||||||
</td>
|
</v-expansion-panel-title>
|
||||||
|
|
||||||
<td
|
<v-divider />
|
||||||
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>
|
<v-expansion-panel-text class="pa-0">
|
||||||
</div>
|
<v-table class="course-table" density="comfortable">
|
||||||
</td>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
</tbody>
|
<th class="day-col bg-primary text-white text-center">星期</th>
|
||||||
</v-table>
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<v-card class="rounded-lg elevation-1 mt-4 overflow-hidden">
|
<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 openPanels = ref([0])
|
||||||
|
|
||||||
|
// 兩張表格預設展開,使用者可分別收合以減少頁面長度。
|
||||||
|
const durationTablePanel = ref<string | null>('duration')
|
||||||
|
const scheduleTablePanel = ref<string | null>('schedule')
|
||||||
|
|
||||||
const GROUP_ILLUSTRATIONS: Record<FitnessGroup, string> = {
|
const GROUP_ILLUSTRATIONS: Record<FitnessGroup, string> = {
|
||||||
low: lowIllustration,
|
low: lowIllustration,
|
||||||
medium: mediumIllustration,
|
medium: mediumIllustration,
|
||||||
@@ -291,7 +318,7 @@ const DAY_NAMES = ['一', '二', '三', '四', '五', '六', '日']
|
|||||||
// 課表小卡:檔名為「星期-時段」,逐一匯入 21 張圖並依 day/slot 查表取用
|
// 課表小卡:檔名為「星期-時段」,逐一匯入 21 張圖並依 day/slot 查表取用
|
||||||
const scheduleCardModules = import.meta.glob<{ default: string }>(
|
const scheduleCardModules = import.meta.glob<{ default: string }>(
|
||||||
'../assets/course/schedule/*.png',
|
'../assets/course/schedule/*.png',
|
||||||
{ eager: true }
|
{ eager: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
const SCHEDULE_CARD_DAY_KEYS = [
|
const SCHEDULE_CARD_DAY_KEYS = [
|
||||||
@@ -313,22 +340,22 @@ const SCHEDULE_CARDS: Record<string, string> = Object.fromEntries(
|
|||||||
Object.entries(scheduleCardModules).map(([path, module]) => [
|
Object.entries(scheduleCardModules).map(([path, module]) => [
|
||||||
path.replace(/^.*\/|\.png$/g, ''),
|
path.replace(/^.*\/|\.png$/g, ''),
|
||||||
module.default,
|
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]}`
|
const key = `${SCHEDULE_CARD_DAY_KEYS[day - 1]}-${SCHEDULE_CARD_SLOT_KEYS[slot]}`
|
||||||
return SCHEDULE_CARDS[key]
|
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: '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: 'noon', label: '中午', icon: 'mdi-white-balance-sunny', headerClass: 'bg-amber-darken-2' },
|
||||||
{ key: 'night', label: '晚上', icon: 'mdi-weather-night', headerClass: 'bg-deep-purple' },
|
{ 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 = 週日)
|
// 今天對應的星期幾,換算成與 CourseRow.day 相同的規則(1 = 週一 … 7 = 週日)
|
||||||
const todayDay = computed(() => {
|
const todayDay = computed(() => {
|
||||||
@@ -339,7 +366,7 @@ const todayDay = computed(() => {
|
|||||||
// v-btn-toggle 以陣列表達勾選狀態,store 則以 Y/N 保存,兩者在此橋接
|
// v-btn-toggle 以陣列表達勾選狀態,store 則以 Y/N 保存,兩者在此橋接
|
||||||
const selectedSlots = computed<TimeSlot[]>({
|
const selectedSlots = computed<TimeSlot[]>({
|
||||||
get: () => store.selectedSlots,
|
get: () => store.selectedSlots,
|
||||||
set: (value) => {
|
set: value => {
|
||||||
for (const slot of SLOTS) {
|
for (const slot of SLOTS) {
|
||||||
const next: YesNo = value.includes(slot.key) ? 'Y' : 'N'
|
const next: YesNo = value.includes(slot.key) ? 'Y' : 'N'
|
||||||
store.setSlot(slot.key, next)
|
store.setSlot(slot.key, next)
|
||||||
@@ -359,6 +386,11 @@ onMounted(() => {
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.course-table-panel-title {
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.day-col {
|
.day-col {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ import 'vuetify/styles'
|
|||||||
const HPTheme: ThemeDefinition = {
|
const HPTheme: ThemeDefinition = {
|
||||||
dark: false,
|
dark: false,
|
||||||
colors: {
|
colors: {
|
||||||
background: '#F0F9F6', // 頁面背景
|
'background': '#F0F9F6', // 頁面背景
|
||||||
surface: '#FFFFFF', // 卡片與白底區塊
|
'surface': '#FFFFFF', // 卡片與白底區塊
|
||||||
primary: '#1D7063', // 主要品牌色
|
'primary': '#1D7063', // 主要品牌色
|
||||||
'primary-darken-1': '#145248',
|
'primary-darken-1': '#145248',
|
||||||
secondary: '#dc5349', // CTA 按鈕
|
'secondary': '#dc5349', // CTA 按鈕
|
||||||
'secondary-darken-1': '#ab4c3d',
|
'secondary-darken-1': '#ab4c3d',
|
||||||
accent: '#47C2A6', // 標籤與強調
|
'accent': '#47C2A6', // 標籤與強調
|
||||||
error: '#B00020',
|
'error': '#B00020',
|
||||||
info: '#2196F3',
|
'info': '#2196F3',
|
||||||
success: '#4CAF50',
|
'success': '#4CAF50',
|
||||||
warning: '#FB8C00',
|
'warning': '#FB8C00',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user