chore: lint

This commit is contained in:
skytek_xinliang
2026-06-22 17:35:23 +08:00
parent 11f309c54c
commit 6da57e1775
8 changed files with 355 additions and 68 deletions
@@ -47,23 +47,58 @@
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 }})
</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 }})
</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 }})
</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 }})
</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 }})
</text>
</svg>
@@ -101,7 +136,7 @@ const centerX = 60
const centerY = 60
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
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 points = []
@@ -138,7 +173,7 @@ const userDataCoordArray = 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>
@@ -295,27 +295,27 @@ const showEmergency = ref(false)
// 無障礙狀態 (可持久化儲存於 localStorage)
const isLargeText = ref(localStorage.getItem('mode-large-text') === 'true')
watch(isLargeText, (newVal) => {
watch(isLargeText, newVal => {
localStorage.setItem('mode-large-text', String(newVal))
})
// 同步底部導航狀態與當前路由
watch(
() => route.path,
(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 }
{ immediate: true },
)
function getBottomNavIconColor(value: string) {
function getBottomNavIconColor (value: string) {
return activeTab.value === value ? 'secondary' : 'secondary-darken-1'
}
function handleLogout() {
function handleLogout () {
store.logout()
router.push('/login')
drawer.value = false
+126 -25
View File
@@ -66,8 +66,7 @@
剩餘名額<span
class="font-weight-bold"
:class="apt.slots < 10 ? 'text-error' : 'text-success'"
>{{ apt.slots }}</span
>
>{{ apt.slots }}</span>
</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 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>
<!-- 紅色掃描雷射線 -->
<div class="laser-line" />
@@ -268,12 +369,12 @@ const selectedApt = ref<Appointment | null>(null)
const filteredAppointments = computed(() => {
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
})
function getStatusColor(status: string) {
function getStatusColor (status: string) {
switch (status) {
case '未報名': {
return 'grey-darken-1'
@@ -290,20 +391,20 @@ function getStatusColor(status: string) {
}
}
function handleRegister(id: string) {
function handleRegister (id: string) {
store.registerAppointment(id)
}
function handleCancel(id: string) {
function handleCancel (id: string) {
store.cancelAppointment(id)
}
function showCheckinQRCode(apt: Appointment) {
function showCheckinQRCode (apt: Appointment) {
selectedApt.value = apt
qrcodeDialog.value = true
}
function simulateOnSiteCheckin() {
function simulateOnSiteCheckin () {
if (selectedApt.value) {
store.checkinAppointment(selectedApt.value.id)
qrcodeDialog.value = false
+11 -12
View File
@@ -36,9 +36,8 @@
<div class="mt-4">
<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-primary"
>{{ completedCount }} / {{ totalCount }} ({{ progressPercent }}%)</span
>
<span class="text-caption font-weight-bold text-primary">{{ completedCount }} / {{ totalCount }} ({{ progressPercent }}%)</span>
</div>
<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-icon class="mb-3" color="warning" icon="mdi-qrcode-scan" size="64" />
<h3 class="text-subtitle-1 font-weight-bold mb-2">尚未在活動現場合對報到</h3>
<p class="text-body-2 text-grey mb-4">
請先前往活動預約頁面出示您的報到條碼給現場志工進行核對
</p>
<v-btn class="rounded-lg" color="primary" to="/appointment">前往查看預約活動</v-btn>
</v-card>
@@ -127,9 +128,7 @@
<!-- 已完成顯示數值 -->
<div v-if="station.completed" class="text-caption text-grey-darken-2">
{{ getMeasurementSummary(key, station.values) }}
<span class="ml-2 text-grey-lighten-1"
>| {{ station.time?.split(' ')[1] }}</span
>
<span class="ml-2 text-grey-lighten-1">| {{ station.time?.split(' ')[1] }}</span>
</div>
<!-- 未完成顯示待測 -->
<div v-else class="text-caption text-grey-darken-1">等待現場檢測員登錄</div>
@@ -199,14 +198,14 @@ const reportSuccessDialog = ref(false)
// 尋找已報到的活動
const activeAppointment = computed(() => {
return store.appointments.find((a) => a.status === '已報到')
return store.appointments.find(a => a.status === '已報到')
})
const completedCount = computed(() => store.completedStationsCount)
const totalCount = computed(() => store.totalStationsCount)
const progressPercent = computed(() => store.progressPercent)
function getStationIcon(code: string) {
function getStationIcon (code: string) {
switch (code) {
case 'BloodPressure': {
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) {
case 'BloodPressure': {
return `收縮壓: ${vals.systolic} / 舒張壓: ${vals.diastolic} mmHg | 心率: ${vals.heartRate} bpm`
@@ -289,7 +288,7 @@ function getMeasurementSummary(code: string, vals: Record<string, any>) {
}
// 模擬一鍵完成所有測量 (對 Demo 极為有用)
function simulateAllMeasurements() {
function simulateAllMeasurements () {
// 自動將剩餘的 9 站填入合理健康的隨機值
const m = store.measurements
@@ -322,12 +321,12 @@ function simulateAllMeasurements() {
}
}
function generateReport() {
function generateReport () {
store.generateFinalReport()
reportSuccessDialog.value = true
}
function goToDashboard() {
function goToDashboard () {
reportSuccessDialog.value = false
router.push('/')
}
@@ -27,9 +27,7 @@
<div>
<h1 class="text-h6 font-weight-black">{{ station.name }}</h1>
<span class="text-caption text-white-70"
> {{ currentStationIndex }} / 11 </span
>
<span class="text-caption text-white-70"> {{ currentStationIndex }} / 11 </span>
</div>
</div>
</v-card-item>
@@ -361,22 +359,22 @@ const formValues = reactive<Record<string, any>>({})
// 監聽 code 改變,初始化表單預設值
watch(
code,
(newCode) => {
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
formValues[field.key]
= existing && existing.completed && existing.values[field.key] !== undefined
? existing.values[field.key]
: field.defaultValue
}
}
},
{ immediate: true }
{ immediate: true },
)
function handleSave() {
function handleSave () {
// 將數據儲存到 store
store.saveMeasurement(code.value, { ...formValues })
+2 -2
View File
@@ -120,7 +120,7 @@ const password = ref('')
const loading = ref(false)
const lineLoading = ref(false)
function handleLogin() {
function handleLogin () {
if (!account.value) return
loading.value = true
setTimeout(() => {
@@ -130,7 +130,7 @@ function handleLogin() {
}, 1000)
}
function _handleLineLogin() {
function _handleLineLogin () {
lineLoading.value = true
setTimeout(() => {
store.login('0912-345-678') // LINE 模擬登入預設手機
@@ -181,6 +181,7 @@
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'"
:icon="getImprovementIcon(imp)"
/>
<div class="text-caption font-weight-bold">{{ imp }}</div>
</v-card>
</v-col>
@@ -342,7 +343,13 @@
</v-btn>
<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>
</div>
@@ -397,7 +404,7 @@ const resultLevel = ref(store.selfAssessment.scoreLevel)
const improvementsOptions = ['肌力', '平衡', '心肺', '柔軟度', '敏捷', '體脂/BMI']
const diseaseOptions = ['高血壓', '糖尿病', '高血脂', '心臟病', '關節退化', '骨質疏鬆']
function getImprovementIcon(imp: string) {
function getImprovementIcon (imp: string) {
switch (imp) {
case '肌力': {
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)
if (index === -1) {
assessmentForm.wantedImprovements.push(imp)
@@ -432,18 +439,18 @@ function toggleImprovement(imp: string) {
}
}
function addMedication() {
function addMedication () {
if (medicationInput.value.trim()) {
assessmentForm.medicationReminders.push(medicationInput.value.trim())
medicationInput.value = ''
}
}
function removeMedication(index: number) {
function removeMedication (index: number) {
assessmentForm.medicationReminders.splice(index, 1)
}
function handleSubmit() {
function handleSubmit () {
// 儲存至 store
store.updateProfile({
name: profileForm.name,
@@ -468,13 +475,13 @@ function handleSubmit() {
currentStep.value = 3
}
function getResultColor(lvl: string) {
function getResultColor (lvl: string) {
if (lvl === '低風險') return 'success'
if (lvl === '需追蹤') return 'warning'
return 'error'
}
function getResultIcon(lvl: string) {
function getResultIcon (lvl: string) {
if (lvl === '低風險') return 'mdi-check-decagram'
if (lvl === '需追蹤') return 'mdi-alert-decagram'
return 'mdi-alert-octagon'
+147
View File
@@ -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/)