feat: draft

This commit is contained in:
skytek_xinliang
2026-06-22 10:59:41 +08:00
commit c2d4c06e7e
38 changed files with 7505 additions and 0 deletions
@@ -0,0 +1,407 @@
<template>
<Layout>
<template #main>
<!-- 頂部導航 -->
<v-btn
class="mb-4 font-weight-bold px-0"
color="primary"
prepend-icon="mdi-arrow-left"
to="/checkin"
variant="text"
>
返回量測站清單
</v-btn>
<!-- 關卡詳細卡片 -->
<v-card
v-if="station"
class="rounded-xl elevation-3 border-accent-light mb-6"
color="surface"
>
<v-card-item class="bg-primary text-white py-4">
<div class="d-flex align-center">
<v-avatar class="mr-3 text-white elevation-1" color="accent" size="48">
<v-icon :icon="station.icon" size="24" />
</v-avatar>
<div>
<h1 class="text-h6 font-weight-black">{{ station.name }}</h1>
<span class="text-caption text-white-70"
> {{ currentStationIndex }} / 11 </span
>
</div>
</div>
</v-card-item>
<v-card-text class="pa-5">
<!-- 關卡簡介 -->
<div class="bg-teal-lighten-5 rounded-lg pa-4 mb-6 text-body-2 text-primary-darken-1">
<div class="font-weight-bold mb-1 d-flex align-center">
<v-icon class="mr-1" icon="mdi-information-outline" size="18" />
檢測說明
</div>
{{ station.description }}
</div>
<!-- 數據輸入表單 -->
<v-form @submit.prevent="handleSave">
<h3 class="text-subtitle-2 font-weight-bold text-grey-darken-3 mb-3">數據錄入</h3>
<v-row dense>
<v-col v-for="field in station.fields" :key="field.key" class="mb-4" cols="12">
<div class="text-body-2 font-weight-bold text-grey-darken-3 mb-1">
{{ field.label }} <span class="text-caption text-grey">({{ field.unit }})</span>
</div>
<v-text-field
v-model.number="formValues[field.key]"
color="primary"
density="comfortable"
hide-details="auto"
:placeholder="field.placeholder"
required
rounded="lg"
:rules="[(v) => (v !== null && v !== undefined) || '此欄位不可為空']"
step="any"
type="number"
variant="outlined"
/>
</v-col>
</v-row>
<v-btn
block
class="rounded-lg font-weight-bold text-white elevation-2 mt-2"
color="secondary"
size="large"
type="submit"
>
儲存並進入下一站
</v-btn>
</v-form>
</v-card-text>
</v-card>
<!-- 參考值與說明卡片 -->
<v-card v-if="station" class="rounded-xl elevation-2 pa-4" color="surface">
<div class="d-flex align-center mb-3 text-primary font-weight-bold text-subtitle-2">
<v-icon class="mr-1" icon="mdi-heart-plus-outline" />
65 歲以上高齡正常參考標準
</div>
<v-divider class="mb-3" />
<div class="text-body-2 text-grey-darken-3 whitespace-pre-line">
{{ station.reference }}
</div>
</v-card>
</template>
</Layout>
</template>
<script setup lang="ts">
import { computed, reactive, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import Layout from '@/components/layout/DefaultLayout.vue'
import { useAppStore } from '@/stores/app'
const route = useRoute()
const router = useRouter()
const store = useAppStore()
const code = computed(() => route.params.code as string)
// 各個測量站點的詳細規格定義
const stationsSpec: Record<
string,
{
name: string
icon: string
description: string
reference: string
fields: Array<{
key: string
label: string
unit: string
placeholder: string
defaultValue: number
}>
}
> = {
BloodPressure: {
name: '血壓與心率量測',
icon: 'mdi-heart-flash',
description:
'請在放鬆狀態下靜坐 5 分鐘,手臂平放於桌面上,將袖帶綁在手肘上方兩公分處,按下測量鈕。檢測員請錄入血壓儀數值。',
reference:
'• 收縮壓 (高壓): 120-139 mmHg (正常偏高),超過 140 mmHg 為高血壓警告。\n• 舒張壓 (低壓): 80-89 mmHg,超過 90 mmHg 為高血壓警告。\n• 靜止心率: 60-100 bpm。',
fields: [
{
key: 'systolic',
label: '收縮壓 (高壓)',
unit: 'mmHg',
placeholder: '如: 125',
defaultValue: 120,
},
{
key: 'diastolic',
label: '舒張壓 (低壓)',
unit: 'mmHg',
placeholder: '如: 80',
defaultValue: 80,
},
{ key: 'heartRate', label: '安靜心率', unit: 'bpm', placeholder: '如: 72', defaultValue: 70 },
],
},
BodyFat: {
name: '體組成分析 (BMI & 體脂)',
icon: 'mdi-scale-bathroom',
description: '請脫鞋站在體脂計上,雙手握緊金屬把手。本站測量體重、BMI 與體脂肪率。',
reference:
'• 高齡者 BMI 正常區間: 20.0 - 26.9 (高齡適度豐腴有助於功能儲備)。\n• 體脂肪率正常區間:\n - 男性: 15% - 25% (超過 25% 為肥胖)\n - 女性: 20% - 30% (超過 30% 為肥胖)',
fields: [
{
key: 'bmi',
label: 'BMI 身體質量指數',
unit: 'kg/m²',
placeholder: '如: 22.5',
defaultValue: 22,
},
{
key: 'bodyFatPct',
label: '體脂肪率',
unit: '%',
placeholder: '如: 24.0',
defaultValue: 25,
},
],
},
GripStrength: {
name: '左右手握力測驗 (肌力)',
icon: 'mdi-hand-back-left',
description:
'受測者自然站立,手持握力器呈 90 度或自然下垂,全力緊握。左右手各測兩次,取最大值錄入。評估上肢最大肌力。',
reference:
'• 高齡男性正常標準: 大於 28 公斤。\n• 高齡女性正常標準: 大於 18 公斤。\n• 數值過低可能代表肌少症前期,需加強抗阻力運動。',
fields: [
{
key: 'leftHand',
label: '左手最大握力',
unit: 'kg',
placeholder: '如: 30',
defaultValue: 0,
},
{
key: 'rightHand',
label: '右手最大握力',
unit: 'kg',
placeholder: '如: 32',
defaultValue: 0,
},
],
},
Sit5x: {
name: '5次坐站測試 (下肢肌力)',
icon: 'mdi-human-male-height',
description:
'受測者雙手抱胸,坐在無扶手椅子上。聽到口令後,以最快速度完成「站起-坐下」5 次。記錄完成的秒數。',
reference:
'• 65 歲以上長者正常標準: 小於 12 秒完成。\n• 若超過 15 秒,表示下肢肌力明顯不足,跌倒風險增高,建議加入股四頭肌訓練。',
fields: [
{
key: 'durationSeconds',
label: '5次坐站完成時間',
unit: '秒',
placeholder: '如: 9.8',
defaultValue: 0,
},
],
},
Sit30s: {
name: '30秒椅子坐立 (肌肉耐力)',
icon: 'mdi-chair-school',
description:
'雙手抱胸交叉於肩前,坐在高度約 43 公分的椅子中間。聽到開始口令後,30 秒內全力完成「完全站立且直立-坐回椅子」的次數。',
reference:
'• 65-69 歲正常標準:\n - 男性: 12 - 18 次\n - 女性: 11 - 17 次\n• 此項目量測長者下肢肌肉耐力,是日常行走和樓梯攀爬的基礎。',
fields: [
{ key: 'count', label: '30秒內坐立次數', unit: '次', placeholder: '如: 14', defaultValue: 0 },
],
},
StepTest: {
name: '2分鐘原地抬膝踏步 (心肺耐力)',
icon: 'mdi-run-fast',
description:
'量測大轉子與髕骨中點高度,在牆上做記號。受測者聽到開始後,原地踏步,大腿必須抬高至記號高度。計算 2 分鐘內「右膝」抬起的次數。',
reference:
'• 65-69 歲正常標準:\n - 男性: 86 - 116 次\n - 女性: 73 - 107 次\n• 評估長者有氧心肺適能,是維持長途行走體力的指標。',
fields: [
{
key: 'count',
label: '2分鐘右膝抬起次數',
unit: '次',
placeholder: '如: 80',
defaultValue: 0,
},
],
},
ArmTest: {
name: '30秒肱二頭肌手臂屈舉 (上肢肌耐力)',
icon: 'mdi-arm-flex',
description:
'坐在椅子上,優勢手持啞鈴 (男性 8 磅 / 女性 5 磅) 自然下垂。聽到開始後,30 秒內以最快速度完成前臂彎舉與放下的次數。',
reference:
'• 65-69 歲正常標準:\n - 男性 (8磅): 15 - 21 次\n - 女性 (5磅): 13 - 19 次\n• 評估上肢日常拿取重物、做家事所需的肌肉耐力。',
fields: [
{
key: 'count',
label: '30秒手臂屈舉次數',
unit: '次',
placeholder: '如: 16',
defaultValue: 0,
},
],
},
BackTest: {
name: '抓背測驗 (上肢關節柔軟度)',
icon: 'mdi-human-stretching',
description:
'一隻手從肩上向後下方伸,另一隻手從腰部往後上方伸,雙手中指儘量靠近並重疊。量測兩中指間距離 (重疊為正,未觸碰為負)。',
reference:
'• 65-69 歲正常標準:\n - 男性: -16 至 -2 公分\n - 女性: -8 至 +3 公分\n• 用於評估肩膀關節的靈活性,攸關穿衣、梳頭等日常自理動作。',
fields: [
{
key: 'leftDistanceCm',
label: '左手在上時兩指間距 (可為負值)',
unit: '公分',
placeholder: '如: -5 或 2',
defaultValue: 0,
},
{
key: 'rightDistanceCm',
label: '右手在上時兩指間距 (可為負值)',
unit: '公分',
placeholder: '如: -3 或 4',
defaultValue: 0,
},
],
},
SitReachTest: {
name: '坐姿體前彎 (下肢與腰部柔軟度)',
icon: 'mdi-human-stretching',
description:
'坐在椅子邊緣,一腳伸直,腳尖勾起,另一腳彎曲平放。吸氣後呼氣,雙手重疊慢慢向前伸觸碰腳尖。測量中指端與腳尖距離。',
reference:
'• 65-69 歲正常標準:\n - 男性: -8 至 +8 公分\n - 女性: -3 至 +12 公分\n• 用於評估大腿後側肌群與腰部的柔軟度,柔軟度佳可有效減緩腰酸背痛並預防拉傷。',
fields: [
{
key: 'distanceCm',
label: '前彎中指與腳尖距離',
unit: '公分',
placeholder: '如: 5 (超過腳尖為正)',
defaultValue: 0,
},
],
},
UpGo8ft: {
name: '2.44公尺起立繞物 (動態平衡與敏捷)',
icon: 'mdi-navigation',
description:
'坐在椅子上,前方 2.44 公尺 (8英尺) 處放置角錐。聽到開始口令後,起身快步繞過角錐並走回椅子坐下。記錄完成秒數。',
reference:
'• 65 歲以上長者正常標準: 小於 8.5 秒完成。\n• 若大於 9.0 秒,代表敏捷度與動態平衡功能衰退,日常過馬路或避開障礙物時跌倒風險顯著升高。',
fields: [
{
key: 'durationSeconds',
label: '起立繞物完成時間',
unit: '秒',
placeholder: '如: 6.2',
defaultValue: 0,
},
],
},
OneLegTest: {
name: '開眼單足立 (靜態平衡力)',
icon: 'mdi-scale-balance',
description:
'受測者雙手叉腰,一腳抬離地面,開始計時。當支撐腳移動、雙手離開腰部或懸空腳觸地時停止。左右腳各測一次,取最大秒數。',
reference:
'• 65 歲以上正常標準: 大於 15 秒。\n• 低於 10 秒代表靜態平衡力嚴重退化,在日常站立、穿褲子或滑倒時缺乏瞬間反應力。',
fields: [
{
key: 'leftSeconds',
label: '左腳站立時間',
unit: '秒',
placeholder: '如: 18',
defaultValue: 0,
},
{
key: 'rightSeconds',
label: '右腳站立時間',
unit: '秒',
placeholder: '如: 20',
defaultValue: 0,
},
],
},
}
const station = computed(() => stationsSpec[code.value])
// 計算當前關卡在 11 關中的順序
const currentStationIndex = computed(() => {
const keys = Object.keys(stationsSpec)
return keys.indexOf(code.value) + 1
})
// 表單綁定的動態狀態
const formValues = reactive<Record<string, any>>({})
// 監聽 code 改變,初始化表單預設值
watch(
code,
(newCode) => {
if (stationsSpec[newCode]) {
// 優先從 store 中讀取已有的測量數據
const existing = store.measurements[newCode]
for (const field of stationsSpec[newCode].fields) {
formValues[field.key] =
existing && existing.completed && existing.values[field.key] !== undefined
? existing.values[field.key]
: field.defaultValue
}
}
},
{ immediate: true }
)
function handleSave() {
// 將數據儲存到 store
store.saveMeasurement(code.value, { ...formValues })
// 尋找下一站
const keys = Object.keys(stationsSpec)
const currentIndex = keys.indexOf(code.value)
if (currentIndex !== -1 && currentIndex < keys.length - 1) {
// 導向下一站
const nextCode = keys[currentIndex + 1]
router.push(`/checkin/${nextCode}`)
} else {
// 已完成最後一站,返回量測清單
router.push('/checkin')
}
}
</script>
<style scoped>
.border-accent-light {
border: 1px solid rgba(71, 194, 166, 0.2) !important;
}
.whitespace-pre-line {
white-space: pre-line;
line-height: 1.6;
}
</style>