chore: lint
This commit is contained in:
@@ -47,23 +47,58 @@
|
|||||||
stroke-width="0.5"
|
stroke-width="0.5"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="middle" x="60" y="8">
|
<text
|
||||||
|
fill="#1D7063"
|
||||||
|
font-size="4"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="middle"
|
||||||
|
x="60"
|
||||||
|
y="8"
|
||||||
|
>
|
||||||
心肺 ({{ radarData.cardio }})
|
心肺 ({{ radarData.cardio }})
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="start" x="110" y="44">
|
<text
|
||||||
|
fill="#1D7063"
|
||||||
|
font-size="4"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="start"
|
||||||
|
x="110"
|
||||||
|
y="44"
|
||||||
|
>
|
||||||
肌力 ({{ radarData.strength }})
|
肌力 ({{ radarData.strength }})
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="start" x="94" y="104">
|
<text
|
||||||
|
fill="#1D7063"
|
||||||
|
font-size="4"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="start"
|
||||||
|
x="94"
|
||||||
|
y="104"
|
||||||
|
>
|
||||||
平衡 ({{ radarData.balance }})
|
平衡 ({{ radarData.balance }})
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="end" x="26" y="104">
|
<text
|
||||||
|
fill="#1D7063"
|
||||||
|
font-size="4"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="end"
|
||||||
|
x="26"
|
||||||
|
y="104"
|
||||||
|
>
|
||||||
柔軟 ({{ radarData.flexibility }})
|
柔軟 ({{ radarData.flexibility }})
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="end" x="10" y="44">
|
<text
|
||||||
|
fill="#1D7063"
|
||||||
|
font-size="4"
|
||||||
|
font-weight="bold"
|
||||||
|
text-anchor="end"
|
||||||
|
x="10"
|
||||||
|
y="44"
|
||||||
|
>
|
||||||
敏捷 ({{ radarData.agility }})
|
敏捷 ({{ radarData.agility }})
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -101,7 +136,7 @@ const centerX = 60
|
|||||||
const centerY = 60
|
const centerY = 60
|
||||||
const radius = 45
|
const radius = 45
|
||||||
|
|
||||||
function getAxisEndPoint(index: number, pointRadius = radius) {
|
function getAxisEndPoint (index: number, pointRadius = radius) {
|
||||||
const angle = -Math.PI / 2 + (index * 2 * Math.PI) / 5
|
const angle = -Math.PI / 2 + (index * 2 * Math.PI) / 5
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -110,7 +145,7 @@ function getAxisEndPoint(index: number, pointRadius = radius) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPolygonPoints(percent: number) {
|
function getPolygonPoints (percent: number) {
|
||||||
const pointRadius = (percent / 100) * radius
|
const pointRadius = (percent / 100) * radius
|
||||||
const points = []
|
const points = []
|
||||||
|
|
||||||
@@ -138,7 +173,7 @@ const userDataCoordArray = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const userDataPoints = computed(() => {
|
const userDataPoints = computed(() => {
|
||||||
return userDataCoordArray.value.map((point) => `${point.x},${point.y}`).join(' ')
|
return userDataCoordArray.value.map(point => `${point.x},${point.y}`).join(' ')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -295,27 +295,27 @@ const showEmergency = ref(false)
|
|||||||
// 無障礙狀態 (可持久化儲存於 localStorage)
|
// 無障礙狀態 (可持久化儲存於 localStorage)
|
||||||
const isLargeText = ref(localStorage.getItem('mode-large-text') === 'true')
|
const isLargeText = ref(localStorage.getItem('mode-large-text') === 'true')
|
||||||
|
|
||||||
watch(isLargeText, (newVal) => {
|
watch(isLargeText, newVal => {
|
||||||
localStorage.setItem('mode-large-text', String(newVal))
|
localStorage.setItem('mode-large-text', String(newVal))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 同步底部導航狀態與當前路由
|
// 同步底部導航狀態與當前路由
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
(path) => {
|
path => {
|
||||||
if (path === '/') activeTab.value = 'home'
|
if (path === '/') activeTab.value = 'home'
|
||||||
else if (path.startsWith('/self-assessment')) activeTab.value = 'self-assessment'
|
else if (path.startsWith('/self-assessment')) activeTab.value = 'self-assessment'
|
||||||
else if (path.startsWith('/appointment')) activeTab.value = 'appointment'
|
else if (path.startsWith('/appointment')) activeTab.value = 'appointment'
|
||||||
else if (path.startsWith('/checkin')) activeTab.value = 'checkin'
|
else if (path.startsWith('/checkin')) activeTab.value = 'checkin'
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
function getBottomNavIconColor(value: string) {
|
function getBottomNavIconColor (value: string) {
|
||||||
return activeTab.value === value ? 'secondary' : 'secondary-darken-1'
|
return activeTab.value === value ? 'secondary' : 'secondary-darken-1'
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogout() {
|
function handleLogout () {
|
||||||
store.logout()
|
store.logout()
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
drawer.value = false
|
drawer.value = false
|
||||||
|
|||||||
@@ -66,8 +66,7 @@
|
|||||||
剩餘名額:<span
|
剩餘名額:<span
|
||||||
class="font-weight-bold"
|
class="font-weight-bold"
|
||||||
:class="apt.slots < 10 ? 'text-error' : 'text-success'"
|
:class="apt.slots < 10 ? 'text-error' : 'text-success'"
|
||||||
>{{ apt.slots }}</span
|
>{{ apt.slots }}</span>
|
||||||
>
|
|
||||||
位
|
位
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,40 +180,142 @@
|
|||||||
<path d="M 0,75 V 100 H 25 V 92 H 8 V 75 Z" fill="#1D7063" />
|
<path d="M 0,75 V 100 H 25 V 92 H 8 V 75 Z" fill="#1D7063" />
|
||||||
<path d="M 92,75 V 92 H 75 V 100 H 100 V 75 Z" fill="#1D7063" />
|
<path d="M 92,75 V 92 H 75 V 100 H 100 V 75 Z" fill="#1D7063" />
|
||||||
<!-- 內嵌二維碼方塊 -->
|
<!-- 內嵌二維碼方塊 -->
|
||||||
<rect fill="#1D7063" height="20" width="20" x="12" y="12" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="20"
|
||||||
|
width="20"
|
||||||
|
x="12"
|
||||||
|
y="12"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="white" height="12" width="12" x="16" y="16" />
|
<rect
|
||||||
|
fill="white"
|
||||||
|
height="12"
|
||||||
|
width="12"
|
||||||
|
x="16"
|
||||||
|
y="16"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#E66926" height="6" width="6" x="19" y="19" />
|
<rect
|
||||||
|
fill="#E66926"
|
||||||
|
height="6"
|
||||||
|
width="6"
|
||||||
|
x="19"
|
||||||
|
y="19"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="20" width="20" x="68" y="12" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="20"
|
||||||
|
width="20"
|
||||||
|
x="68"
|
||||||
|
y="12"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="white" height="12" width="12" x="72" y="16" />
|
<rect
|
||||||
|
fill="white"
|
||||||
|
height="12"
|
||||||
|
width="12"
|
||||||
|
x="72"
|
||||||
|
y="16"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#E66926" height="6" width="6" x="75" y="75" />
|
<rect
|
||||||
|
fill="#E66926"
|
||||||
|
height="6"
|
||||||
|
width="6"
|
||||||
|
x="75"
|
||||||
|
y="75"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="20" width="20" x="12" y="68" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="20"
|
||||||
|
width="20"
|
||||||
|
x="12"
|
||||||
|
y="68"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="white" height="12" width="12" x="16" y="72" />
|
<rect
|
||||||
|
fill="white"
|
||||||
|
height="12"
|
||||||
|
width="12"
|
||||||
|
x="16"
|
||||||
|
y="72"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="6" width="6" x="19" y="75" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="6"
|
||||||
|
width="6"
|
||||||
|
x="19"
|
||||||
|
y="75"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 中間隨機小方塊 -->
|
<!-- 中間隨機小方塊 -->
|
||||||
<rect fill="#1D7063" height="8" width="8" x="40" y="20" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="8"
|
||||||
|
width="8"
|
||||||
|
x="40"
|
||||||
|
y="20"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#E66926" height="14" width="6" x="52" y="12" />
|
<rect
|
||||||
|
fill="#E66926"
|
||||||
|
height="14"
|
||||||
|
width="6"
|
||||||
|
x="52"
|
||||||
|
y="12"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="6" width="16" x="42" y="36" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="6"
|
||||||
|
width="16"
|
||||||
|
x="42"
|
||||||
|
y="36"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#47C2A6" height="10" width="28" x="36" y="48" />
|
<rect
|
||||||
|
fill="#47C2A6"
|
||||||
|
height="10"
|
||||||
|
width="28"
|
||||||
|
x="36"
|
||||||
|
y="48"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="12" width="14" x="68" y="42" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="12"
|
||||||
|
width="14"
|
||||||
|
x="68"
|
||||||
|
y="42"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#E66926" height="16" width="16" x="72" y="60" />
|
<rect
|
||||||
|
fill="#E66926"
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
x="72"
|
||||||
|
y="60"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#1D7063" height="12" width="18" x="44" y="68" />
|
<rect
|
||||||
|
fill="#1D7063"
|
||||||
|
height="12"
|
||||||
|
width="18"
|
||||||
|
x="44"
|
||||||
|
y="68"
|
||||||
|
/>
|
||||||
|
|
||||||
<rect fill="#47C2A6" height="6" width="24" x="40" y="82" />
|
<rect
|
||||||
|
fill="#47C2A6"
|
||||||
|
height="6"
|
||||||
|
width="24"
|
||||||
|
x="40"
|
||||||
|
y="82"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- 紅色掃描雷射線 -->
|
<!-- 紅色掃描雷射線 -->
|
||||||
<div class="laser-line" />
|
<div class="laser-line" />
|
||||||
@@ -268,12 +369,12 @@ const selectedApt = ref<Appointment | null>(null)
|
|||||||
|
|
||||||
const filteredAppointments = computed(() => {
|
const filteredAppointments = computed(() => {
|
||||||
if (filterTab.value === 'registered') {
|
if (filterTab.value === 'registered') {
|
||||||
return store.appointments.filter((a) => a.status === '已報名' || a.status === '已報到')
|
return store.appointments.filter(a => a.status === '已報名' || a.status === '已報到')
|
||||||
}
|
}
|
||||||
return store.appointments
|
return store.appointments
|
||||||
})
|
})
|
||||||
|
|
||||||
function getStatusColor(status: string) {
|
function getStatusColor (status: string) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case '未報名': {
|
case '未報名': {
|
||||||
return 'grey-darken-1'
|
return 'grey-darken-1'
|
||||||
@@ -290,20 +391,20 @@ function getStatusColor(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRegister(id: string) {
|
function handleRegister (id: string) {
|
||||||
store.registerAppointment(id)
|
store.registerAppointment(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel(id: string) {
|
function handleCancel (id: string) {
|
||||||
store.cancelAppointment(id)
|
store.cancelAppointment(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCheckinQRCode(apt: Appointment) {
|
function showCheckinQRCode (apt: Appointment) {
|
||||||
selectedApt.value = apt
|
selectedApt.value = apt
|
||||||
qrcodeDialog.value = true
|
qrcodeDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function simulateOnSiteCheckin() {
|
function simulateOnSiteCheckin () {
|
||||||
if (selectedApt.value) {
|
if (selectedApt.value) {
|
||||||
store.checkinAppointment(selectedApt.value.id)
|
store.checkinAppointment(selectedApt.value.id)
|
||||||
qrcodeDialog.value = false
|
qrcodeDialog.value = false
|
||||||
|
|||||||
@@ -36,9 +36,8 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="d-flex justify-space-between align-center mb-1">
|
<div class="d-flex justify-space-between align-center mb-1">
|
||||||
<span class="text-caption font-weight-bold text-grey-darken-2">測量站點完成度</span>
|
<span class="text-caption font-weight-bold text-grey-darken-2">測量站點完成度</span>
|
||||||
<span class="text-caption font-weight-bold text-primary"
|
|
||||||
>{{ completedCount }} / {{ totalCount }} ({{ progressPercent }}%)</span
|
<span class="text-caption font-weight-bold text-primary">{{ completedCount }} / {{ totalCount }} ({{ progressPercent }}%)</span>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-progress-linear
|
<v-progress-linear
|
||||||
@@ -57,9 +56,11 @@
|
|||||||
<v-card v-else class="rounded-xl elevation-3 pa-6 text-center mb-6" color="surface">
|
<v-card v-else class="rounded-xl elevation-3 pa-6 text-center mb-6" color="surface">
|
||||||
<v-icon class="mb-3" color="warning" icon="mdi-qrcode-scan" size="64" />
|
<v-icon class="mb-3" color="warning" icon="mdi-qrcode-scan" size="64" />
|
||||||
<h3 class="text-subtitle-1 font-weight-bold mb-2">尚未在活動現場合對報到</h3>
|
<h3 class="text-subtitle-1 font-weight-bold mb-2">尚未在活動現場合對報到</h3>
|
||||||
|
|
||||||
<p class="text-body-2 text-grey mb-4">
|
<p class="text-body-2 text-grey mb-4">
|
||||||
請先前往「活動預約」頁面,出示您的報到條碼給現場志工進行核對。
|
請先前往「活動預約」頁面,出示您的報到條碼給現場志工進行核對。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<v-btn class="rounded-lg" color="primary" to="/appointment">前往查看預約活動</v-btn>
|
<v-btn class="rounded-lg" color="primary" to="/appointment">前往查看預約活動</v-btn>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
@@ -127,9 +128,7 @@
|
|||||||
<!-- 已完成,顯示數值 -->
|
<!-- 已完成,顯示數值 -->
|
||||||
<div v-if="station.completed" class="text-caption text-grey-darken-2">
|
<div v-if="station.completed" class="text-caption text-grey-darken-2">
|
||||||
{{ getMeasurementSummary(key, station.values) }}
|
{{ getMeasurementSummary(key, station.values) }}
|
||||||
<span class="ml-2 text-grey-lighten-1"
|
<span class="ml-2 text-grey-lighten-1">| {{ station.time?.split(' ')[1] }}</span>
|
||||||
>| {{ station.time?.split(' ')[1] }}</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 未完成,顯示待測 -->
|
<!-- 未完成,顯示待測 -->
|
||||||
<div v-else class="text-caption text-grey-darken-1">等待現場檢測員登錄</div>
|
<div v-else class="text-caption text-grey-darken-1">等待現場檢測員登錄</div>
|
||||||
@@ -199,14 +198,14 @@ const reportSuccessDialog = ref(false)
|
|||||||
|
|
||||||
// 尋找已報到的活動
|
// 尋找已報到的活動
|
||||||
const activeAppointment = computed(() => {
|
const activeAppointment = computed(() => {
|
||||||
return store.appointments.find((a) => a.status === '已報到')
|
return store.appointments.find(a => a.status === '已報到')
|
||||||
})
|
})
|
||||||
|
|
||||||
const completedCount = computed(() => store.completedStationsCount)
|
const completedCount = computed(() => store.completedStationsCount)
|
||||||
const totalCount = computed(() => store.totalStationsCount)
|
const totalCount = computed(() => store.totalStationsCount)
|
||||||
const progressPercent = computed(() => store.progressPercent)
|
const progressPercent = computed(() => store.progressPercent)
|
||||||
|
|
||||||
function getStationIcon(code: string) {
|
function getStationIcon (code: string) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'BloodPressure': {
|
case 'BloodPressure': {
|
||||||
return 'mdi-heart-flash'
|
return 'mdi-heart-flash'
|
||||||
@@ -247,7 +246,7 @@ function getStationIcon(code: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMeasurementSummary(code: string, vals: Record<string, any>) {
|
function getMeasurementSummary (code: string, vals: Record<string, any>) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'BloodPressure': {
|
case 'BloodPressure': {
|
||||||
return `收縮壓: ${vals.systolic} / 舒張壓: ${vals.diastolic} mmHg | 心率: ${vals.heartRate} bpm`
|
return `收縮壓: ${vals.systolic} / 舒張壓: ${vals.diastolic} mmHg | 心率: ${vals.heartRate} bpm`
|
||||||
@@ -289,7 +288,7 @@ function getMeasurementSummary(code: string, vals: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 模擬一鍵完成所有測量 (對 Demo 极為有用)
|
// 模擬一鍵完成所有測量 (對 Demo 极為有用)
|
||||||
function simulateAllMeasurements() {
|
function simulateAllMeasurements () {
|
||||||
// 自動將剩餘的 9 站填入合理健康的隨機值
|
// 自動將剩餘的 9 站填入合理健康的隨機值
|
||||||
const m = store.measurements
|
const m = store.measurements
|
||||||
|
|
||||||
@@ -322,12 +321,12 @@ function simulateAllMeasurements() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateReport() {
|
function generateReport () {
|
||||||
store.generateFinalReport()
|
store.generateFinalReport()
|
||||||
reportSuccessDialog.value = true
|
reportSuccessDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToDashboard() {
|
function goToDashboard () {
|
||||||
reportSuccessDialog.value = false
|
reportSuccessDialog.value = false
|
||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="text-h6 font-weight-black">{{ station.name }}</h1>
|
<h1 class="text-h6 font-weight-black">{{ station.name }}</h1>
|
||||||
|
|
||||||
<span class="text-caption text-white-70"
|
<span class="text-caption text-white-70">第 {{ currentStationIndex }} 站 / 共 11 站</span>
|
||||||
>第 {{ currentStationIndex }} 站 / 共 11 站</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
@@ -361,22 +359,22 @@ const formValues = reactive<Record<string, any>>({})
|
|||||||
// 監聽 code 改變,初始化表單預設值
|
// 監聽 code 改變,初始化表單預設值
|
||||||
watch(
|
watch(
|
||||||
code,
|
code,
|
||||||
(newCode) => {
|
newCode => {
|
||||||
if (stationsSpec[newCode]) {
|
if (stationsSpec[newCode]) {
|
||||||
// 優先從 store 中讀取已有的測量數據
|
// 優先從 store 中讀取已有的測量數據
|
||||||
const existing = store.measurements[newCode]
|
const existing = store.measurements[newCode]
|
||||||
for (const field of stationsSpec[newCode].fields) {
|
for (const field of stationsSpec[newCode].fields) {
|
||||||
formValues[field.key] =
|
formValues[field.key]
|
||||||
existing && existing.completed && existing.values[field.key] !== undefined
|
= existing && existing.completed && existing.values[field.key] !== undefined
|
||||||
? existing.values[field.key]
|
? existing.values[field.key]
|
||||||
: field.defaultValue
|
: field.defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
function handleSave() {
|
function handleSave () {
|
||||||
// 將數據儲存到 store
|
// 將數據儲存到 store
|
||||||
store.saveMeasurement(code.value, { ...formValues })
|
store.saveMeasurement(code.value, { ...formValues })
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const password = ref('')
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const lineLoading = ref(false)
|
const lineLoading = ref(false)
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin () {
|
||||||
if (!account.value) return
|
if (!account.value) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -130,7 +130,7 @@ function handleLogin() {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function _handleLineLogin() {
|
function _handleLineLogin () {
|
||||||
lineLoading.value = true
|
lineLoading.value = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
store.login('0912-345-678') // LINE 模擬登入預設手機
|
store.login('0912-345-678') // LINE 模擬登入預設手機
|
||||||
|
|||||||
@@ -181,6 +181,7 @@
|
|||||||
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'"
|
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'"
|
||||||
:icon="getImprovementIcon(imp)"
|
:icon="getImprovementIcon(imp)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="text-caption font-weight-bold">{{ imp }}</div>
|
<div class="text-caption font-weight-bold">{{ imp }}</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -342,7 +343,13 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<div v-else class="w-100 d-flex gap-2">
|
<div v-else class="w-100 d-flex gap-2">
|
||||||
<v-btn block class="rounded-lg py-2" color="primary" to="/appointment" variant="flat">
|
<v-btn
|
||||||
|
block
|
||||||
|
class="rounded-lg py-2"
|
||||||
|
color="primary"
|
||||||
|
to="/appointment"
|
||||||
|
variant="flat"
|
||||||
|
>
|
||||||
立即預約普測
|
立即預約普測
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
@@ -397,7 +404,7 @@ const resultLevel = ref(store.selfAssessment.scoreLevel)
|
|||||||
const improvementsOptions = ['肌力', '平衡', '心肺', '柔軟度', '敏捷', '體脂/BMI']
|
const improvementsOptions = ['肌力', '平衡', '心肺', '柔軟度', '敏捷', '體脂/BMI']
|
||||||
const diseaseOptions = ['高血壓', '糖尿病', '高血脂', '心臟病', '關節退化', '骨質疏鬆']
|
const diseaseOptions = ['高血壓', '糖尿病', '高血脂', '心臟病', '關節退化', '骨質疏鬆']
|
||||||
|
|
||||||
function getImprovementIcon(imp: string) {
|
function getImprovementIcon (imp: string) {
|
||||||
switch (imp) {
|
switch (imp) {
|
||||||
case '肌力': {
|
case '肌力': {
|
||||||
return 'mdi-arm-flex'
|
return 'mdi-arm-flex'
|
||||||
@@ -423,7 +430,7 @@ function getImprovementIcon(imp: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleImprovement(imp: string) {
|
function toggleImprovement (imp: string) {
|
||||||
const index = assessmentForm.wantedImprovements.indexOf(imp)
|
const index = assessmentForm.wantedImprovements.indexOf(imp)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
assessmentForm.wantedImprovements.push(imp)
|
assessmentForm.wantedImprovements.push(imp)
|
||||||
@@ -432,18 +439,18 @@ function toggleImprovement(imp: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMedication() {
|
function addMedication () {
|
||||||
if (medicationInput.value.trim()) {
|
if (medicationInput.value.trim()) {
|
||||||
assessmentForm.medicationReminders.push(medicationInput.value.trim())
|
assessmentForm.medicationReminders.push(medicationInput.value.trim())
|
||||||
medicationInput.value = ''
|
medicationInput.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMedication(index: number) {
|
function removeMedication (index: number) {
|
||||||
assessmentForm.medicationReminders.splice(index, 1)
|
assessmentForm.medicationReminders.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit () {
|
||||||
// 儲存至 store
|
// 儲存至 store
|
||||||
store.updateProfile({
|
store.updateProfile({
|
||||||
name: profileForm.name,
|
name: profileForm.name,
|
||||||
@@ -468,13 +475,13 @@ function handleSubmit() {
|
|||||||
currentStep.value = 3
|
currentStep.value = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResultColor(lvl: string) {
|
function getResultColor (lvl: string) {
|
||||||
if (lvl === '低風險') return 'success'
|
if (lvl === '低風險') return 'success'
|
||||||
if (lvl === '需追蹤') return 'warning'
|
if (lvl === '需追蹤') return 'warning'
|
||||||
return 'error'
|
return 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResultIcon(lvl: string) {
|
function getResultIcon (lvl: string) {
|
||||||
if (lvl === '低風險') return 'mdi-check-decagram'
|
if (lvl === '低風險') return 'mdi-check-decagram'
|
||||||
if (lvl === '需追蹤') return 'mdi-alert-decagram'
|
if (lvl === '需追蹤') return 'mdi-alert-decagram'
|
||||||
return 'mdi-alert-octagon'
|
return 'mdi-alert-octagon'
|
||||||
|
|||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
# 運動玩轉健康力
|
||||||
|
|
||||||
|
## 頁面路由結構
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── /login
|
||||||
|
├── /self-assessment
|
||||||
|
├── /appointment
|
||||||
|
├── /checkin
|
||||||
|
└── /checkin/:code
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由對應頁面
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── / 首頁 / 市民端主控台
|
||||||
|
├── /login 登入頁
|
||||||
|
├── /self-assessment 線上自評頁
|
||||||
|
├── /appointment 預約場次頁
|
||||||
|
├── /checkin 現場報到與量測清單頁
|
||||||
|
└── /checkin/:code 量測子頁面
|
||||||
|
├── /checkin/BloodPressure
|
||||||
|
├── /checkin/BodyFat
|
||||||
|
├── /checkin/GripStrength
|
||||||
|
├── /checkin/Sit5x
|
||||||
|
├── /checkin/Sit30s
|
||||||
|
├── /checkin/StepTest
|
||||||
|
├── /checkin/ArmTest
|
||||||
|
├── /checkin/BackTest
|
||||||
|
├── /checkin/SitReachTest
|
||||||
|
├── /checkin/UpGo8ft
|
||||||
|
├── /checkin/OneLegTest
|
||||||
|
└── /checkin/ResultReview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 備註
|
||||||
|
|
||||||
|
- `/checkin/:code` 使用同一個 Vue 頁面,透過 route param 顯示不同量測站內容。
|
||||||
|
- 目前路由來源以 [src/router/index.ts](../health-platform/hp-frontend/src/router/index.ts:1) 為準。
|
||||||
|
|
||||||
|
## 預計功能
|
||||||
|
|
||||||
|
1. 帳號與身分
|
||||||
|
|
||||||
|
- 手機號碼登入
|
||||||
|
- 基本資料維護:姓名、生日、性別、身高、體重、聯絡方式
|
||||||
|
- 緊急聯絡人
|
||||||
|
- 個資同意與資料授權狀態
|
||||||
|
|
||||||
|
2. 健康自評
|
||||||
|
|
||||||
|
- 3 分鐘線上自評問卷
|
||||||
|
- 運動頻率、疼痛狀況、跌倒史、慢性病、用藥提醒
|
||||||
|
- 生活功能自評:走路、上下樓、起身、平衡、柔軟度
|
||||||
|
- 自評結果分級:低風險 / 需追蹤 / 建議諮詢
|
||||||
|
|
||||||
|
3. 活動與預約
|
||||||
|
|
||||||
|
- 公益普測活動列表
|
||||||
|
- 活動詳情:地點、時間、名額、主辦單位、量測項目
|
||||||
|
- 線上報名 / 取消報名
|
||||||
|
- QR Code 報到
|
||||||
|
- 報名紀錄與到場狀態
|
||||||
|
|
||||||
|
4. 現場量測結果
|
||||||
|
|
||||||
|
- 血壓 BloodPressure
|
||||||
|
- 體脂 BodyFat
|
||||||
|
- 握力 GripStrength
|
||||||
|
- 5 次坐站 Sit5x
|
||||||
|
- 30 秒椅子坐立 Sit30s
|
||||||
|
- 原地抬膝踏步 StepTest
|
||||||
|
- 肱二頭肌手臂屈舉 ArmTest
|
||||||
|
- 抓背測驗 BackTest
|
||||||
|
- 坐姿體前彎 SitReachTest
|
||||||
|
- 2.44 公尺椅子坐立繞物 UpGo8ft
|
||||||
|
- 開眼單足立 OneLegTest
|
||||||
|
|
||||||
|
5. 個人健康力儀表板
|
||||||
|
|
||||||
|
- 健康力總分
|
||||||
|
- 肌力、平衡、柔軟、敏捷、心肺等能力分項
|
||||||
|
- 與同年齡區間參考值比較
|
||||||
|
- 風險提示:跌倒風險、血壓異常、肌力不足等
|
||||||
|
- 最近一次量測摘要
|
||||||
|
- 歷史趨勢圖
|
||||||
|
|
||||||
|
6. 運動處方
|
||||||
|
|
||||||
|
- 依量測結果產生建議訓練
|
||||||
|
- 每週訓練任務
|
||||||
|
- 動作說明:次數、組數、頻率、注意事項
|
||||||
|
- 禁忌提醒:血壓過高、疼痛、暈眩時暫停
|
||||||
|
- 訓練完成回填
|
||||||
|
|
||||||
|
7. 追蹤與提醒
|
||||||
|
|
||||||
|
- 下次建議量測時間
|
||||||
|
- 回訪提醒
|
||||||
|
- 運動任務提醒
|
||||||
|
- 異常數值提醒
|
||||||
|
- 報名活動前提醒
|
||||||
|
|
||||||
|
8. 健康紀錄
|
||||||
|
|
||||||
|
- 每次自評紀錄
|
||||||
|
- 每次量測紀錄
|
||||||
|
- 運動任務完成紀錄
|
||||||
|
- 健康報告下載 / 分享
|
||||||
|
- 可出示給教練、社區據點或照護人員的摘要頁
|
||||||
|
|
||||||
|
9. 家屬 / 照護者協作
|
||||||
|
|
||||||
|
- 授權家屬查看摘要
|
||||||
|
- 家屬提醒通知
|
||||||
|
- 協助預約活動
|
||||||
|
- 緊急聯絡資訊更新
|
||||||
|
|
||||||
|
10. 無障礙與高齡友善
|
||||||
|
|
||||||
|
- 大字模式
|
||||||
|
- 高對比模式
|
||||||
|
- 簡化操作流程
|
||||||
|
- 語音提示 / 朗讀友善
|
||||||
|
- 明確的下一步按鈕
|
||||||
|
- 表單錯誤提示清楚可讀
|
||||||
|
|
||||||
|
## 使用者流程
|
||||||
|
|
||||||
|
登入 / 建檔 → 線上自評 → 預約普測 → QR Code 報到 → 查看量測結果 → 取得運動處方 → 追蹤回訪
|
||||||
|
|
||||||
|
訪客進站
|
||||||
|
→ 點擊「開始自評」
|
||||||
|
→ 建立帳號 / LINE 登入 / 手機驗證
|
||||||
|
→ 基本資料與同意書
|
||||||
|
→ 自評問卷
|
||||||
|
→ 顯示初步分級
|
||||||
|
→ 推薦普測或課程
|
||||||
|
→ 預約場館 / 社區據點
|
||||||
|
→ 報到 QR Code
|
||||||
|
→ 完成普測與運動處方
|
||||||
|
→ 4 週 / 12 週提醒回測
|
||||||
|
→ 個人報告與獎勵點數
|
||||||
|
|
||||||
|
[cloudflare](https://health-platform-8u1.pages.dev/)
|
||||||
Reference in New Issue
Block a user