440 lines
12 KiB
Vue
440 lines
12 KiB
Vue
<template>
|
||
<v-app :class="{ 'large-text-mode': isLargeText }">
|
||
<!-- 頂部導航欄 -->
|
||
<v-app-bar class="px-2 elevation-1" color="primary" flat>
|
||
<template #prepend>
|
||
<v-app-bar-nav-icon variant="text" @click="drawer = !drawer" />
|
||
</template>
|
||
|
||
<v-app-bar-title class="font-weight-bold flex-grow-1">
|
||
運動玩轉健康力
|
||
</v-app-bar-title>
|
||
|
||
<template #append>
|
||
<div class="d-flex align-center">
|
||
<!-- 快捷無障礙切換 -->
|
||
<v-btn
|
||
class="mr-1"
|
||
icon
|
||
size="small"
|
||
title="切換大字體"
|
||
variant="text"
|
||
@click="isLargeText = !isLargeText"
|
||
>
|
||
<v-icon
|
||
:color="isLargeText ? 'accent' : 'white'"
|
||
icon="mdi-format-size"
|
||
/>
|
||
</v-btn>
|
||
</div>
|
||
</template>
|
||
</v-app-bar>
|
||
|
||
<!-- 側邊抽屜導航 -->
|
||
<v-navigation-drawer
|
||
v-model="drawer"
|
||
class="bg-background"
|
||
location="left"
|
||
temporary
|
||
width="300"
|
||
>
|
||
<v-list-item v-if="store.isLoggedIn" class="py-4 bg-primary text-white">
|
||
<template #prepend>
|
||
<v-avatar color="accent" size="48">
|
||
<svg
|
||
aria-hidden="true"
|
||
class="profile-avatar-icon"
|
||
viewBox="0 0 24 24"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
>
|
||
<path
|
||
d="M11.518,12.629l.559,.377c1.134-.847,6.754-4.326,7-8.706,0-2.371-1.794-4.3-4-4.3-1.193,0-2.267,.565-3,1.458-.733-.894-1.807-1.458-3-1.458-2.206,0-4,1.929-4,4.3,0,3.876,5.782,7.885,6.441,8.329ZM9.077,2c1.103,0,2,1.032,2,2.3h2c0-1.268,.897-2.3,2-2.3s2,1.032,2,2.3c0,1.948-2.864,4.71-4.998,6.275-1.732-1.286-5.002-4.147-5.002-6.275,0-1.268,.897-2.3,2-2.3Zm14.923,17h-1v3h-2V14h2v3h1v2Zm-6-7h2v12h-2v-5H6v5h-2V12h2v5h12v-5ZM1,14H3v8H1v-3H0v-2H1v-3Z"
|
||
/>
|
||
</svg>
|
||
</v-avatar>
|
||
</template>
|
||
|
||
<v-list-item-subtitle class="text-white-50">{{
|
||
store.userProfile.phone
|
||
}}</v-list-item-subtitle>
|
||
</v-list-item>
|
||
|
||
<v-list-item v-else class="py-2 bg-primary text-white">
|
||
<template #prepend>
|
||
<v-avatar color="accent" size="48">
|
||
<v-icon color="white" icon="mdi-heart-pulse" />
|
||
</v-avatar>
|
||
</template>
|
||
|
||
<v-list-item-subtitle class="text-white-50"
|
||
>未登入訪客</v-list-item-subtitle
|
||
>
|
||
</v-list-item>
|
||
|
||
<v-divider />
|
||
|
||
<v-list-item>
|
||
<template #prepend>
|
||
<v-icon color="primary" icon="mdi-format-size" />
|
||
</template>
|
||
|
||
<v-list-item-title>大字體模式</v-list-item-title>
|
||
|
||
<template #append>
|
||
<v-switch
|
||
v-model="isLargeText"
|
||
color="secondary"
|
||
density="compact"
|
||
hide-details
|
||
/>
|
||
</template>
|
||
</v-list-item>
|
||
|
||
<v-divider class="my-2" />
|
||
|
||
<!-- 主要功能連結 -->
|
||
<v-list density="comfortable" nav>
|
||
<v-list-item
|
||
class="py-2"
|
||
color="primary"
|
||
exact
|
||
prepend-icon="mdi-view-dashboard"
|
||
title="計畫簡介"
|
||
to="/"
|
||
/>
|
||
|
||
<!-- <v-list-item
|
||
v-if="store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-account-edit"
|
||
title="個人資料修改"
|
||
to="/profile"
|
||
@click="drawer = false"
|
||
/> -->
|
||
|
||
<v-list-item
|
||
v-if="SELF_ASSESSMENT_ENABLED && store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-clipboard-text-search"
|
||
title="自評"
|
||
to="/self-assessment"
|
||
/>
|
||
<v-list-item
|
||
v-if="BODY_COMPOSITION_ENABLED && store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-scale-bathroom"
|
||
title="身體數值分析"
|
||
to="/body-composition"
|
||
@click="drawer = false"
|
||
/>
|
||
|
||
<v-list-item
|
||
v-if="store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-calendar-week"
|
||
title="課程建議"
|
||
to="/course"
|
||
/>
|
||
|
||
<v-list-item
|
||
v-if="APPOINTMENT_ENABLED && store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-calendar-check"
|
||
title="普測活動預約"
|
||
to="/appointment"
|
||
/>
|
||
|
||
<v-list-item
|
||
v-if="CHECKIN_ENABLED && store.isLoggedIn"
|
||
color="primary"
|
||
prepend-icon="mdi-qrcode-scan"
|
||
title="現場報到與量測"
|
||
to="/checkin"
|
||
/>
|
||
</v-list>
|
||
|
||
<template v-if="store.isLoggedIn">
|
||
<v-divider class="my-2" />
|
||
|
||
<v-list density="compact" nav>
|
||
<v-list-item
|
||
v-if="store.requiresEmailCompletion"
|
||
color="warning"
|
||
prepend-icon="mdi-email-alert-outline"
|
||
title="註冊 E-Mail"
|
||
:to="emailCompletionLocation"
|
||
@click="drawer = false"
|
||
/>
|
||
<v-list-item
|
||
color="primary"
|
||
prepend-icon="mdi-shield-check"
|
||
title="資料授權與隱私條款"
|
||
@click="showPrivacy = true"
|
||
/>
|
||
</v-list>
|
||
</template>
|
||
|
||
<template #append>
|
||
<div class="pa-4">
|
||
<v-btn
|
||
v-if="store.isLoggedIn"
|
||
block
|
||
color="error"
|
||
prepend-icon="mdi-logout"
|
||
variant="tonal"
|
||
@click="handleLogout"
|
||
>
|
||
登出系統
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
v-else
|
||
block
|
||
color="secondary"
|
||
prepend-icon="mdi-login"
|
||
to="/login"
|
||
variant="flat"
|
||
@click="drawer = false"
|
||
>
|
||
登入健康平台
|
||
</v-btn>
|
||
</div>
|
||
</template>
|
||
</v-navigation-drawer>
|
||
|
||
<!-- 主要內容區 -->
|
||
<v-main
|
||
class="bg-white min-vh-100"
|
||
:class="{ 'pb-16': BOTTOM_NAVIGATION_ENABLED }"
|
||
>
|
||
<v-container class="px-4 py-2">
|
||
<v-alert
|
||
v-if="
|
||
store.isLoggedIn &&
|
||
store.requiresEmailCompletion &&
|
||
route.name !== 'complete-email'
|
||
"
|
||
class="mb-2"
|
||
color="warning"
|
||
icon="mdi-email-alert-outline"
|
||
variant="tonal"
|
||
>
|
||
<div class="d-flex flex-wrap align-center justify-space-between ga-2">
|
||
<span class="text-title-small">
|
||
{{
|
||
EMAIL_COMPLETION_REQUIRED
|
||
? "請先註冊 E-Mail,才能繼續使用平台。"
|
||
: "尚未設定 E-Mail,前往註冊以接收重要通知。"
|
||
}}
|
||
</span>
|
||
<v-btn
|
||
color="warning"
|
||
size="small"
|
||
:to="emailCompletionLocation"
|
||
variant="flat"
|
||
>
|
||
前往補登
|
||
</v-btn>
|
||
</div>
|
||
</v-alert>
|
||
<slot name="main" />
|
||
</v-container>
|
||
</v-main>
|
||
|
||
<!-- 底部導航欄 (Mobile First 核心) -->
|
||
<v-bottom-navigation
|
||
v-if="BOTTOM_NAVIGATION_ENABLED"
|
||
v-model="activeTab"
|
||
density="compact"
|
||
bg-color="background"
|
||
color="primary-darken-1"
|
||
grow
|
||
tag="div"
|
||
>
|
||
<v-btn to="/" value="home">
|
||
<v-icon :color="getBottomNavIconColor('home')"
|
||
>mdi-view-dashboard</v-icon
|
||
>
|
||
<span class="bottom-nav-text">首頁</span>
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
v-if="SELF_ASSESSMENT_ENABLED && store.isLoggedIn"
|
||
to="/self-assessment"
|
||
value="self-assessment"
|
||
>
|
||
<v-icon :color="getBottomNavIconColor('self-assessment')">
|
||
mdi-clipboard-text-search
|
||
</v-icon>
|
||
|
||
<span class="bottom-nav-text">自評</span>
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
v-if="CHECKIN_ENABLED && store.isLoggedIn"
|
||
to="/checkin"
|
||
value="checkin"
|
||
>
|
||
<v-icon :color="getBottomNavIconColor('checkin')"
|
||
>mdi-qrcode-scan</v-icon
|
||
>
|
||
<span class="bottom-nav-text">量測</span>
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
v-if="APPOINTMENT_ENABLED && store.isLoggedIn"
|
||
to="/appointment"
|
||
value="appointment"
|
||
>
|
||
<v-icon :color="getBottomNavIconColor('appointment')"
|
||
>mdi-calendar-check</v-icon
|
||
>
|
||
<span class="bottom-nav-text">預約</span>
|
||
</v-btn>
|
||
</v-bottom-navigation>
|
||
|
||
<!-- 隱私授權對話框 -->
|
||
<v-dialog v-model="showPrivacy" max-width="500">
|
||
<v-card class="rounded-xl">
|
||
<v-card-title
|
||
class="bg-primary text-white font-weight-bold d-flex align-center"
|
||
>
|
||
<v-icon class="mr-2" icon="mdi-shield-check" />
|
||
個資同意與資料授權
|
||
</v-card-title>
|
||
|
||
<v-card-text class="pt-4 text-body-large">
|
||
<p class="mb-3">
|
||
「運動玩轉健康力」平台非常重視您的個人隱私。我們所蒐集的身體活動自評、血壓、體脂以及現場運動表現數據,將僅用於以下用途:
|
||
</p>
|
||
|
||
<ul class="pl-4 mb-3">
|
||
<li>提供個人化的運動與健康改善建議。</li>
|
||
<li>於活動現場合對您的身分。</li>
|
||
<li>在去識別化的安全機制下,作為高齡健康促進的政策分析參考。</li>
|
||
</ul>
|
||
</v-card-text>
|
||
|
||
<v-card-actions class="justify-end px-4 pb-4">
|
||
<v-btn
|
||
class="rounded-lg px-4"
|
||
color="primary"
|
||
variant="flat"
|
||
@click="showPrivacy = false"
|
||
>
|
||
確認關閉
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
</v-app>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, watch } from "vue";
|
||
import { useRoute, useRouter } from "vue-router";
|
||
import {
|
||
APPOINTMENT_ENABLED,
|
||
BODY_COMPOSITION_ENABLED,
|
||
BOTTOM_NAVIGATION_ENABLED,
|
||
CHECKIN_ENABLED,
|
||
EMAIL_COMPLETION_REQUIRED,
|
||
SELF_ASSESSMENT_ENABLED,
|
||
} from "@/config/features";
|
||
import { useAppStore } from "@/stores/app";
|
||
import { useCourseStore } from "@/stores/course";
|
||
|
||
const store = useAppStore();
|
||
const courseStore = useCourseStore();
|
||
const router = useRouter();
|
||
const route = useRoute();
|
||
|
||
const drawer = ref(false);
|
||
const activeTab = ref("home");
|
||
const showPrivacy = ref(false);
|
||
const emailCompletionLocation = computed(() => ({
|
||
name: "complete-email",
|
||
query: {
|
||
redirect: route.name === "complete-email" ? "/" : route.fullPath,
|
||
},
|
||
}));
|
||
|
||
// 無障礙狀態 (可持久化儲存於 localStorage)
|
||
const isLargeText = ref(localStorage.getItem("mode-large-text") === "true");
|
||
|
||
watch(isLargeText, (newVal) => {
|
||
localStorage.setItem("mode-large-text", String(newVal));
|
||
});
|
||
|
||
// 同步底部導航狀態與當前路由
|
||
watch(
|
||
() => route.path,
|
||
(path) => {
|
||
if (path === "/") activeTab.value = "home";
|
||
else if (path.startsWith("/self-assessment"))
|
||
activeTab.value = "self-assessment";
|
||
else if (path.startsWith("/appointment")) activeTab.value = "appointment";
|
||
else if (path.startsWith("/checkin")) activeTab.value = "checkin";
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
function getBottomNavIconColor(value: string) {
|
||
return activeTab.value === value ? "secondary" : "secondary-darken-1";
|
||
}
|
||
|
||
async function handleLogout() {
|
||
await store.logout();
|
||
courseStore.resetGroup();
|
||
await router.push("/login");
|
||
drawer.value = false;
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
// 行動優先的寬度限制,確保在大螢幕上也不會跑版
|
||
.max-width-mobile {
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
// 大字體模式
|
||
.large-text-mode {
|
||
font-size: 1.15rem !important;
|
||
|
||
.v-list-item-title,
|
||
.v-btn,
|
||
.text-title-medium {
|
||
font-size: 1.25rem !important;
|
||
line-height: unset !important;
|
||
}
|
||
.v-card-text,
|
||
.text-body-large,
|
||
.text-body-medium,
|
||
p {
|
||
font-size: 1.15rem !important;
|
||
line-height: 1.6 !important;
|
||
}
|
||
.text-h4,
|
||
.text-h5,
|
||
.text-h6 {
|
||
font-size: 1.5rem !important;
|
||
}
|
||
|
||
.bottom-nav-text {
|
||
font-size: 1rem;
|
||
}
|
||
}
|
||
|
||
.border-top {
|
||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.profile-avatar-icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
color: white;
|
||
fill: currentcolor;
|
||
}
|
||
</style>
|