feat: draft
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
<template>
|
||||
<v-app :class="{ 'large-text-mode': isLargeText, 'high-contrast-mode': isHighContrast }">
|
||||
<!-- 頂部導航欄 -->
|
||||
<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">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2 text-accent" icon="mdi-heart-pulse" />
|
||||
<span class="text-subtitle-1 text-sm-h6 font-weight-bold">運動玩轉健康力</span>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<v-avatar
|
||||
v-if="store.isLoggedIn"
|
||||
class="elevation-1 border-white"
|
||||
color="accent"
|
||||
size="36"
|
||||
@click="drawer = !drawer"
|
||||
>
|
||||
<span class="text-white font-weight-bold text-caption">{{
|
||||
store.userProfile.name.substring(1)
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
|
||||
<v-btn
|
||||
v-else
|
||||
class="font-weight-bold"
|
||||
color="accent"
|
||||
prepend-icon="mdi-login"
|
||||
size="small"
|
||||
to="/login"
|
||||
variant="flat"
|
||||
>
|
||||
登入
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
|
||||
<!-- 側邊抽屜導航 -->
|
||||
<v-navigation-drawer v-model="drawer" 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">
|
||||
<span class="text-white font-weight-bold text-h6">{{
|
||||
store.userProfile.name.substring(1)
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
</template>
|
||||
|
||||
<v-list-item-title class="font-weight-bold">{{ store.userProfile.name }} 先生</v-list-item-title>
|
||||
|
||||
<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-4 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-title class="font-weight-bold">運動玩轉健康力</v-list-item-title>
|
||||
|
||||
<v-list-item-subtitle class="text-white-50">未登入訪客</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- 高齡與無障礙設定區 -->
|
||||
<v-list-subheader class="font-weight-bold text-primary">高齡友善設定</v-list-subheader>
|
||||
|
||||
<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-list-item>
|
||||
<template #prepend>
|
||||
<v-icon color="primary" icon="mdi-contrast-box" />
|
||||
</template>
|
||||
|
||||
<v-list-item-title>高對比模式</v-list-item-title>
|
||||
|
||||
<template #append>
|
||||
<v-switch v-model="isHighContrast" color="secondary" density="compact" hide-details />
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<!-- 主要功能連結 -->
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item
|
||||
color="primary"
|
||||
exact
|
||||
prepend-icon="mdi-view-dashboard"
|
||||
title="首頁 / 主控台"
|
||||
to="/"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
color="primary"
|
||||
prepend-icon="mdi-clipboard-text-search"
|
||||
title="線上健康自評"
|
||||
to="/self-assessment"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
v-if="store.isLoggedIn"
|
||||
color="primary"
|
||||
prepend-icon="mdi-calendar-check"
|
||||
title="普測活動預約"
|
||||
to="/appointment"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
v-if="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
|
||||
color="primary"
|
||||
prepend-icon="mdi-shield-check"
|
||||
title="資料授權與隱私條款"
|
||||
@click="showPrivacy = true"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
color="primary"
|
||||
prepend-icon="mdi-phone"
|
||||
title="緊急聯絡人資訊"
|
||||
@click="showEmergency = 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="primary"
|
||||
prepend-icon="mdi-login"
|
||||
to="/login"
|
||||
variant="flat"
|
||||
@click="drawer = false"
|
||||
>
|
||||
登入健康平台
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<!-- 主要內容區 -->
|
||||
<v-main class="bg-background min-vh-100 pb-16">
|
||||
<v-container class="px-4 py-4 max-width-mobile" fluid>
|
||||
<slot name="main" />
|
||||
</v-container>
|
||||
</v-main>
|
||||
|
||||
<!-- 底部導航欄 (Mobile First 核心) -->
|
||||
<v-bottom-navigation
|
||||
v-model="activeTab"
|
||||
class="elevation-8 border-top"
|
||||
color="primary"
|
||||
grow
|
||||
style="position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000"
|
||||
>
|
||||
<v-btn to="/" value="home">
|
||||
<v-icon>mdi-view-dashboard</v-icon>
|
||||
<span>主控台</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn to="/self-assessment" value="self-assessment">
|
||||
<v-icon>mdi-clipboard-text-search</v-icon>
|
||||
<span>線上自評</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="store.isLoggedIn" to="/appointment" value="appointment">
|
||||
<v-icon>mdi-calendar-check</v-icon>
|
||||
<span>活動預約</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="store.isLoggedIn" to="/checkin" value="checkin">
|
||||
<v-icon>mdi-qrcode-scan</v-icon>
|
||||
<span>現場量測</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-1">
|
||||
<p class="mb-3">
|
||||
「運動玩轉健康力」平台非常重視您的個人隱私。我們所蒐集的身體活動自評、血壓、體脂以及現場運動表現數據,將僅用於以下用途:
|
||||
</p>
|
||||
|
||||
<ul class="pl-4 mb-3">
|
||||
<li>提供個人化的運動處方與健康改善建議。</li>
|
||||
<li>於普測活動現場合對您的身分。</li>
|
||||
<li>在去識別化的安全機制下,作為高齡健康促進的政策分析參考。</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-caption text-grey">
|
||||
授權狀態:{{ store.userProfile.consent ? '已同意授權' : '未授權' }}
|
||||
</p>
|
||||
</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-dialog v-model="showEmergency" 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-alert-circle-outline" />
|
||||
緊急聯絡資訊
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pt-4 text-body-1">
|
||||
<v-text-field
|
||||
v-model="store.userProfile.emergencyContact"
|
||||
class="mb-3"
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
label="緊急聯絡人姓名"
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="store.userProfile.emergencyPhone"
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
label="緊急聯絡人電話"
|
||||
variant="outlined"
|
||||
/>
|
||||
</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="showEmergency = false"
|
||||
>儲存並關閉</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const store = useAppStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const drawer = ref(false)
|
||||
const activeTab = ref('home')
|
||||
const showPrivacy = ref(false)
|
||||
const showEmergency = ref(false)
|
||||
|
||||
// 無障礙狀態 (可持久化儲存於 localStorage)
|
||||
const isLargeText = ref(localStorage.getItem('mode-large-text') === 'true')
|
||||
const isHighContrast = ref(localStorage.getItem('mode-high-contrast') === 'true')
|
||||
|
||||
watch(isLargeText, newVal => {
|
||||
localStorage.setItem('mode-large-text', String(newVal))
|
||||
})
|
||||
|
||||
watch(isHighContrast, newVal => {
|
||||
localStorage.setItem('mode-high-contrast', 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 handleLogout () {
|
||||
store.logout()
|
||||
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,
|
||||
.v-card-title,
|
||||
.text-subtitle-1 {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
.v-card-text,
|
||||
.text-body-1,
|
||||
.text-body-2,
|
||||
p {
|
||||
font-size: 1.15rem !important;
|
||||
line-height: 1.6 !important;
|
||||
}
|
||||
.text-h4,
|
||||
.text-h5,
|
||||
.text-h6 {
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 高對比模式
|
||||
.high-contrast-mode {
|
||||
--v-theme-background: #000000 !important;
|
||||
--v-theme-surface: #121212 !important;
|
||||
--v-theme-primary: #ffeb3b !important; // 明亮黃色作為主色
|
||||
--v-theme-accent: #00e676 !important;
|
||||
--v-theme-secondary: #ff9100 !important;
|
||||
|
||||
// 文字高對比
|
||||
.text-primary,
|
||||
.v-list-item-title,
|
||||
.v-btn,
|
||||
.v-card-title,
|
||||
.v-app-bar-title,
|
||||
span,
|
||||
p,
|
||||
div {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.v-app-bar {
|
||||
border-bottom: 2px solid #ffeb3b !important;
|
||||
}
|
||||
|
||||
.v-bottom-navigation {
|
||||
border-top: 2px solid #ffeb3b !important;
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user