feat: 首頁介紹
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,345 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import introductionImage from "@/assets/index/introduction_0916.png";
|
||||
import { PLATFORM_INTRODUCTION } from "@/data/platformIntroduction";
|
||||
|
||||
const expandedSections = ref([0, 1, 2, 3]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="platform-introduction"
|
||||
aria-labelledby="platform-introduction-title"
|
||||
>
|
||||
<div class="introduction-image-frame">
|
||||
<v-img
|
||||
alt="運動玩轉健康力全齡功能性體適能檢測平台資訊圖"
|
||||
class="introduction-image"
|
||||
:src="introductionImage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-card class="introduction-card" variant="flat">
|
||||
<v-card-text class="pa-5 pa-sm-7">
|
||||
<div class="introduction-heading">
|
||||
<span class="eyebrow">{{ PLATFORM_INTRODUCTION.eyebrow }}</span>
|
||||
<h2 id="platform-introduction-title" class="introduction-title">
|
||||
{{ PLATFORM_INTRODUCTION.title }}
|
||||
</h2>
|
||||
<p class="introduction-subtitle">
|
||||
{{ PLATFORM_INTRODUCTION.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="journey-list" aria-label="全齡健康旅程">
|
||||
<template
|
||||
v-for="(journey, index) in PLATFORM_INTRODUCTION.journey"
|
||||
:key="journey"
|
||||
>
|
||||
<span class="journey-item">{{ journey }}</span>
|
||||
<v-icon
|
||||
v-if="index < PLATFORM_INTRODUCTION.journey.length - 1"
|
||||
class="journey-arrow"
|
||||
color="secondary"
|
||||
icon="mdi-arrow-right"
|
||||
size="18"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<v-expansion-panels v-model="expandedSections" class="mt-6" multiple>
|
||||
<v-expansion-panel
|
||||
v-for="(section, index) in PLATFORM_INTRODUCTION.sections"
|
||||
:key="section.title"
|
||||
class="introduction-panel"
|
||||
:value="index"
|
||||
>
|
||||
<v-expansion-panel-title class="px-4 py-3">
|
||||
<div class="d-flex align-center ga-3">
|
||||
<span class="section-number">{{
|
||||
String(index + 1).padStart(2, "0")
|
||||
}}</span>
|
||||
<span class="font-weight-bold text-primary">{{
|
||||
section.title
|
||||
}}</span>
|
||||
</div>
|
||||
</v-expansion-panel-title>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<p v-if="section.intro" class="section-intro">
|
||||
{{ section.intro }}
|
||||
</p>
|
||||
|
||||
<ol v-if="section.steps" class="service-steps">
|
||||
<li
|
||||
v-for="(step, stepIndex) in section.steps"
|
||||
:key="step.title"
|
||||
>
|
||||
<span class="step-number">{{ stepIndex + 1 }}</span>
|
||||
<div>
|
||||
<div class="font-weight-bold text-primary">
|
||||
{{ step.title }}
|
||||
</div>
|
||||
<div class="text-grey-darken-1">{{ step.detail }}</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div v-if="section.features" class="feature-grid">
|
||||
<div
|
||||
v-for="(feature, featureIndex) in section.features"
|
||||
:key="feature.title"
|
||||
class="feature-item"
|
||||
>
|
||||
<span class="feature-number">{{
|
||||
String(featureIndex + 1).padStart(2, "0")
|
||||
}}</span>
|
||||
<div class="font-weight-bold text-primary">
|
||||
{{ feature.title }}
|
||||
</div>
|
||||
<div class="text-grey-darken-1">{{ feature.detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="section.audiences" class="audience-list">
|
||||
<div
|
||||
v-for="audience in section.audiences"
|
||||
:key="audience.title"
|
||||
class="audience-item"
|
||||
>
|
||||
<div class="font-weight-bold text-primary mb-2">
|
||||
{{ audience.title }}
|
||||
</div>
|
||||
<ul class="content-list">
|
||||
<li v-for="item in audience.items" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul v-if="section.items" class="content-list">
|
||||
<li v-for="item in section.items" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
|
||||
<p v-if="section.outro" class="section-outro">
|
||||
{{ section.outro }}
|
||||
</p>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.platform-introduction {
|
||||
max-width: 760px;
|
||||
margin: 24px auto 40px;
|
||||
}
|
||||
|
||||
.introduction-image-frame {
|
||||
padding: 6px;
|
||||
border-radius: 28px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(29, 112, 99, 0.16),
|
||||
rgba(255, 163, 49, 0.22)
|
||||
);
|
||||
box-shadow: 0 14px 34px rgba(20, 82, 72, 0.12);
|
||||
}
|
||||
|
||||
.introduction-image {
|
||||
border-radius: 23px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.introduction-card {
|
||||
margin-top: 20px;
|
||||
border: 1px solid rgba(29, 112, 99, 0.12);
|
||||
border-radius: 28px;
|
||||
background: linear-gradient(160deg, #f6fcf9 0%, #ffffff 38%);
|
||||
}
|
||||
|
||||
.introduction-heading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.introduction-title {
|
||||
margin: 14px 0 4px;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
font-size: clamp(1.75rem, 5vw, 2.5rem);
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.introduction-subtitle {
|
||||
margin: 0;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.journey-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.journey-item {
|
||||
padding: 8px 13px;
|
||||
border-radius: 12px;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 3px 10px rgba(20, 82, 72, 0.08);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.journey-arrow {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.introduction-panel {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid rgba(29, 112, 99, 0.1);
|
||||
border-radius: 16px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-number,
|
||||
.feature-number,
|
||||
.step-number {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
background-color: rgb(var(--v-theme-secondary));
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.section-number {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.section-intro,
|
||||
.section-outro {
|
||||
margin: 0 0 14px;
|
||||
color: rgb(var(--v-theme-primary));
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.section-outro {
|
||||
margin: 14px 0 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.content-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding-left: 22px;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.content-list li::marker {
|
||||
color: rgb(var(--v-theme-secondary));
|
||||
}
|
||||
|
||||
.service-steps {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.service-steps li {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.feature-item,
|
||||
.audience-item {
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background-color: rgba(var(--v-theme-primary), 0.06);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.feature-item:nth-child(even) {
|
||||
background-color: rgba(var(--v-theme-secondary), 0.11);
|
||||
}
|
||||
|
||||
.feature-number {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 9px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.audience-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.platform-introduction {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.introduction-image-frame,
|
||||
.introduction-card {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.introduction-image {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.journey-list {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.journey-item {
|
||||
padding: 7px 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
export const APPOINTMENT_ENABLED = false;
|
||||
export const BOTTOM_NAVIGATION_ENABLED = false;
|
||||
export const CHECKIN_ENABLED = false;
|
||||
export const EXERCISE_DURATION_RECOMMENDATION_ENABLED = true;
|
||||
export const RADAR_ENABLED = false;
|
||||
export const SELF_ASSESSMENT_ENABLED = false;
|
||||
export const APPOINTMENT_ENABLED = false
|
||||
export const BOTTOM_NAVIGATION_ENABLED = false
|
||||
export const CHECKIN_ENABLED = false
|
||||
export const EXERCISE_DURATION_RECOMMENDATION_ENABLED = false
|
||||
export const RADAR_ENABLED = false
|
||||
export const SELF_ASSESSMENT_ENABLED = false
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"title": "運動玩轉健康力",
|
||||
"subtitle": "全齡功能性體適能檢測平台簡介",
|
||||
"eyebrow": "全齡功能性體適能檢測平台",
|
||||
"journey": ["看見狀態", "知道怎麼做", "持續回來"],
|
||||
"sections": [
|
||||
{
|
||||
"title": "平台定位",
|
||||
"items": [
|
||||
"以「運動 × AI × 數據 × 健康」為核心,建置全齡功能性體適能檢測與健康促進平台。",
|
||||
"服務對象涵蓋兒童、青壯年、中高齡及銀髮族。",
|
||||
"結合科學檢測、數位分析、體適能分級、運動建議及持續追蹤。",
|
||||
"將一次性的體適能檢測,轉化為可執行、可追蹤、可持續的健康行動。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "核心理念",
|
||||
"items": [
|
||||
"秉持「把數據變成行動,把行動變成習慣」的推動理念。",
|
||||
"協助參與者看見身體的真實狀態,了解自身優勢與需要改善的能力。",
|
||||
"依據個人身體狀態提供安全、適合且能夠持續執行的運動方式。",
|
||||
"強調沒有任何訓練目標高於身體的真實狀態,讓健康促進建立在安全與可恢復的基礎上。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "11項功能性體適能測驗",
|
||||
"intro": "平台整合11項功能性體適能測驗,評估面向包括:",
|
||||
"items": [
|
||||
"上肢肌力、下肢肌力。",
|
||||
"上肢柔軟度、下肢柔軟度。",
|
||||
"敏捷與移動能力、靜態平衡能力。",
|
||||
"心肺耐力、日常生活功能表現。"
|
||||
],
|
||||
"outro": "透過多面向檢測,協助參與者掌握目前的身體功能、潛在風險及需要優先改善的項目。"
|
||||
},
|
||||
{
|
||||
"title": "平台服務流程",
|
||||
"steps": [
|
||||
{
|
||||
"title": "功能檢測",
|
||||
"detail": "完成11項功能性體適能測驗,建立個人基礎數據。"
|
||||
},
|
||||
{
|
||||
"title": "數據分析",
|
||||
"detail": "系統整合各項測驗結果,呈現個人身體功能表現。"
|
||||
},
|
||||
{
|
||||
"title": "體適能分級",
|
||||
"detail": "依據個人檢測結果進行綜合評估與體適能分級。"
|
||||
},
|
||||
{
|
||||
"title": "運動建議",
|
||||
"detail": "依照身體狀態提供有氧、肌力、平衡及柔軟伸展等建議。"
|
||||
},
|
||||
{
|
||||
"title": "行動課表",
|
||||
"detail": "將運動建議轉化為一週每日行動課表,提供清楚、容易執行的健康指引。"
|
||||
},
|
||||
{
|
||||
"title": "定期複測",
|
||||
"detail": "透過定期回測與前後數據比較,追蹤身體功能及運動成效。"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "四大核心功能",
|
||||
"features": [
|
||||
{ "title": "11項功能測驗", "detail": "一次掌握身體功能與目前狀態。" },
|
||||
{
|
||||
"title": "個人化體適能分級",
|
||||
"detail": "快速看懂自身優勢、潛在風險及需要加強的能力。"
|
||||
},
|
||||
{
|
||||
"title": "精準運動建議",
|
||||
"detail": "依據檢測結果安排有氧、肌力、平衡及伸展訓練。"
|
||||
},
|
||||
{
|
||||
"title": "每週行動課表",
|
||||
"detail": "提供早、中、晚清楚指引,協助建立可持續的運動習慣。"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "數位健康履歷",
|
||||
"items": [
|
||||
"保存個人歷次功能性體適能檢測結果,建立個人專屬的數位健康紀錄。",
|
||||
"比較檢測前後的身體功能變化,掌握運動介入及課程訓練成效。",
|
||||
"提升參與者的運動持續性與自我健康管理能力。",
|
||||
"作為後續課程調整、健康追蹤及定期複測的參考依據。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "平台應用場域",
|
||||
"items": [
|
||||
"社區健康促進活動、各級學校與教育場域。",
|
||||
"企業員工健康管理、運動中心與健身場域。",
|
||||
"樂齡學習及銀髮服務據點、長照機構與社會福利單位。",
|
||||
"醫療及健康促進合作場域,以及ESG、CSR及公益普測活動。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "平台使用價值",
|
||||
"audiences": [
|
||||
{
|
||||
"title": "對參與者",
|
||||
"items": [
|
||||
"認識自己的身體狀態。",
|
||||
"找出需要優先改善的能力。",
|
||||
"獲得適合自己的運動方向。",
|
||||
"看見運動前後的身體改變,建立安全且可持續的健康習慣。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "對教練及專業人員",
|
||||
"items": [
|
||||
"掌握參與者的功能表現與風險。",
|
||||
"作為課程分級及內容調整的依據。",
|
||||
"提供個人化運動與訓練建議,必要時即時調整或停止不適合的訓練。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "對場域及政策推動單位",
|
||||
"items": [
|
||||
"建立可量化的健康促進成果。",
|
||||
"支援課程規劃、成效管理及資源配置。",
|
||||
"建立可追蹤、可複製、可擴散的服務模式。",
|
||||
"在資料去識別化及適當授權下,作為健康促進政策分析參考。"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "全齡健康旅程",
|
||||
"intro": "平台陪伴每一位參與者完成三個重要階段:",
|
||||
"items": [
|
||||
"看見狀態:了解現在的身體功能與健康狀況。",
|
||||
"知道怎麼做:找到安全、適合且可以實際執行的運動方式。",
|
||||
"持續回來:透過運動、追蹤及定期複測,逐步建立健康習慣。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "平台願景",
|
||||
"intro": "「運動玩轉健康力」不只提供一次檢測的分數,更希望透過科學檢測、數據分析、行動課表與持續追蹤,陪伴每一個人建立長期健康生活。",
|
||||
"outro": "健康無價,從了解自己的身體開始。讓運動成為習慣,讓健康成為日常。"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import platformIntroduction from "./platform-introduction.json";
|
||||
|
||||
export interface IntroductionStep {
|
||||
title: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export interface IntroductionFeature {
|
||||
title: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export interface IntroductionAudience {
|
||||
title: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
export interface IntroductionSection {
|
||||
title: string;
|
||||
intro?: string;
|
||||
items?: string[];
|
||||
outro?: string;
|
||||
steps?: IntroductionStep[];
|
||||
features?: IntroductionFeature[];
|
||||
audiences?: IntroductionAudience[];
|
||||
}
|
||||
|
||||
export interface PlatformIntroductionContent {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
eyebrow: string;
|
||||
journey: string[];
|
||||
sections: IntroductionSection[];
|
||||
}
|
||||
|
||||
export const PLATFORM_INTRODUCTION =
|
||||
platformIntroduction as PlatformIntroductionContent;
|
||||
@@ -274,6 +274,8 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<PlatformIntroduction />
|
||||
|
||||
<v-dialog v-model="generateDialog" max-width="400">
|
||||
<v-card class="rounded-xl overflow-hidden">
|
||||
<v-card-title class="bg-primary text-white font-weight-bold py-3">
|
||||
@@ -404,6 +406,7 @@ import BrandAvatar from "@/components/BrandAvatar.vue";
|
||||
import HealthRadarChart from "@/components/HealthRadarChart.vue";
|
||||
import Layout from "@/components/layout/DefaultLayout.vue";
|
||||
import ModuleRouterCard from "@/components/ModuleRouterCard.vue";
|
||||
import PlatformIntroduction from "@/components/PlatformIntroduction.vue";
|
||||
import PhysicalFitnessSummary from "@/components/PhysicalFitnessSummary.vue";
|
||||
import { useDisplayUserName } from "@/composables/useDisplayUserName";
|
||||
import {
|
||||
@@ -428,7 +431,6 @@ const physicalFitnessItems = ref<PhysicalFitnessItem[]>([]);
|
||||
const physicalFitnessReceivedAt = ref<string | null>(null);
|
||||
const physicalFitnessLoading = ref(false);
|
||||
const physicalFitnessError = ref("");
|
||||
const redirectingToLogin = ref(false);
|
||||
|
||||
const displayUserName = useDisplayUserName(store.userProfile);
|
||||
|
||||
@@ -498,11 +500,6 @@ function getRequiredUserID(): string | null {
|
||||
return userID;
|
||||
}
|
||||
|
||||
if (!redirectingToLogin.value) {
|
||||
redirectingToLogin.value = true;
|
||||
void router.replace({ name: "login", query: { redirect: "/" } });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user