style: index page
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This repository is a private pnpm workspace. Workspace packages are declared in `pnpm-workspace.yaml` under `app/*`.
|
||||
|
||||
The active application is `app/citizen-frontend`, a Vue 3 + Vite + Vuetify frontend written in TypeScript. Its source lives in `app/citizen-frontend/src/`:
|
||||
|
||||
- `src/main.ts` bootstraps the app and global styles.
|
||||
- `src/App.vue` hosts the router view.
|
||||
- `src/pages/` contains route-level pages.
|
||||
- `src/components/` contains reusable Vue components.
|
||||
- `src/stores/` contains Pinia stores.
|
||||
- `src/router/` contains Vue Router configuration.
|
||||
- `src/assets/` stores static assets such as local fonts.
|
||||
- `src/styles/` contains global style and Vuetify settings.
|
||||
|
||||
If a nested `AGENTS.md` exists, follow the closest one for files in that subtree.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Run commands from `app/citizen-frontend` unless noted otherwise.
|
||||
|
||||
- `pnpm install`: install workspace dependencies.
|
||||
- `pnpm dev`: start the Vite development server.
|
||||
- `pnpm build`: run type-checking and production build.
|
||||
- `pnpm build-only`: build the Vite app without type-checking.
|
||||
- `pnpm type-check`: run `vue-tsc`.
|
||||
- `pnpm lint`: run ESLint.
|
||||
- `pnpm lint:fix`: apply ESLint auto-fixes.
|
||||
- `pnpm preview`: preview the production build locally.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Use TypeScript, Vue 3 Composition API, and `<script setup>` for Vue components. Follow the existing Vuetify component style and route/page organization. Use 2-space indentation, single quotes, no semicolons, and trailing commas where the linter expects them.
|
||||
|
||||
Name Vue pages and components descriptively, matching existing patterns such as `self-assessment.vue`, `DefaultLayout.vue`, and Pinia stores under `src/stores/`.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
No dedicated test suite is currently configured. For changes, run at minimum:
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
pnpm type-check
|
||||
```
|
||||
|
||||
For UI or routing changes, also run `pnpm build` and manually verify the affected flows in the browser.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Recent commits use short Conventional Commit-style prefixes, for example `feat:`, `style:`, and `restyle:`. Keep commit messages concise and scoped to the change.
|
||||
|
||||
Pull requests should include a short summary, affected paths, verification commands run, and screenshots or screen recordings for visible UI changes. Link related issues when available.
|
||||
|
||||
## Security & Configuration Tips
|
||||
|
||||
Do not commit secrets or environment-specific credentials. Keep third-party licenses with bundled assets, especially files under `src/assets/`.
|
||||
@@ -9,3 +9,7 @@
|
||||
- Framework: Vue 3 + Vite
|
||||
- UI Library: Vuetify
|
||||
- Enabled Features: ESLint, Pinia, Vue I18n, Vue Router
|
||||
|
||||
## UI Construction
|
||||
- Prefer Vuetify built-in UI components and utility classes when building pages.
|
||||
- Use custom CSS only when Vuetify components or utilities cannot express the required layout, spacing, color, or interaction clearly.
|
||||
|
||||
@@ -9,5 +9,5 @@ export default vuetify(
|
||||
'vue/script-indent': 'off',
|
||||
'vue/html-self-closing': 'off',
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"lint:fix": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.2.10",
|
||||
"@mdi/font": "7.4.47",
|
||||
"vue": "^3.5.30",
|
||||
"vuetify": "^4.0.2",
|
||||
@@ -29,16 +28,10 @@
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"typescript": "~5.9.3",
|
||||
"unplugin-fonts": "^1.4.0",
|
||||
"vite": "^8.0.0",
|
||||
"vite-plugin-vuetify": "^2.1.3",
|
||||
"vue-tsc": "^3.2.5",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-vuetify": "^4.3.4"
|
||||
},
|
||||
"overrides": {
|
||||
"unplugin-fonts": {
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="d-flex justify-center my-2 position-relative">
|
||||
<svg class="radar-svg" height="260" viewBox="0 0 120 120" width="260">
|
||||
<polygon
|
||||
v-for="grid in [20, 40, 60, 80, 100]"
|
||||
:key="grid"
|
||||
fill="none"
|
||||
:points="getPolygonPoints(grid)"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
/>
|
||||
|
||||
<line
|
||||
v-for="i in 5"
|
||||
:key="`axis-${i}`"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
x1="60"
|
||||
:x2="getAxisEndPoint(i - 1).x"
|
||||
y1="60"
|
||||
:y2="getAxisEndPoint(i - 1).y"
|
||||
/>
|
||||
|
||||
<polygon
|
||||
fill="rgba(33, 150, 243, 0.12)"
|
||||
:points="getPolygonPoints(65)"
|
||||
stroke="#2196F3"
|
||||
stroke-dasharray="1 1"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<polygon
|
||||
fill="rgba(71, 194, 166, 0.35)"
|
||||
:points="userDataPoints"
|
||||
stroke="#1D7063"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
|
||||
<circle
|
||||
v-for="(point, index) in userDataCoordArray"
|
||||
:key="`dot-${index}`"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
fill="#E66926"
|
||||
r="1.5"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<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">
|
||||
肌力 ({{ radarData.strength }})
|
||||
</text>
|
||||
|
||||
<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">
|
||||
柔軟 ({{ radarData.flexibility }})
|
||||
</text>
|
||||
|
||||
<text fill="#1D7063" font-size="4" font-weight="bold" text-anchor="end" x="10" y="44">
|
||||
敏捷 ({{ radarData.agility }})
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<!-- <div class="radar-legend d-flex flex-column">
|
||||
<div class="d-flex align-center text-caption mb-1">
|
||||
<span class="legend-color bg-primary mr-1" />
|
||||
<span>我的指標</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center text-caption">
|
||||
<span class="legend-color bg-blue mr-1 border-dashed" />
|
||||
<span>同齡參考</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface RadarData {
|
||||
strength: number
|
||||
balance: number
|
||||
flexibility: number
|
||||
agility: number
|
||||
cardio: number
|
||||
}
|
||||
|
||||
const { radarData } = defineProps<{
|
||||
radarData: RadarData
|
||||
}>()
|
||||
|
||||
const centerX = 60
|
||||
const centerY = 60
|
||||
const radius = 45
|
||||
|
||||
function getAxisEndPoint(index: number, pointRadius = radius) {
|
||||
const angle = -Math.PI / 2 + (index * 2 * Math.PI) / 5
|
||||
|
||||
return {
|
||||
x: centerX + pointRadius * Math.cos(angle),
|
||||
y: centerY + pointRadius * Math.sin(angle),
|
||||
}
|
||||
}
|
||||
|
||||
function getPolygonPoints(percent: number) {
|
||||
const pointRadius = (percent / 100) * radius
|
||||
const points = []
|
||||
|
||||
for (let index = 0; index < 5; index++) {
|
||||
const point = getAxisEndPoint(index, pointRadius)
|
||||
points.push(`${point.x},${point.y}`)
|
||||
}
|
||||
|
||||
return points.join(' ')
|
||||
}
|
||||
|
||||
const userDataCoordArray = computed(() => {
|
||||
const values = [
|
||||
radarData.cardio,
|
||||
radarData.strength,
|
||||
radarData.balance,
|
||||
radarData.flexibility,
|
||||
radarData.agility,
|
||||
]
|
||||
|
||||
return values.map((value, index) => {
|
||||
const pointRadius = (Math.max(5, Math.min(100, value)) / 100) * radius
|
||||
return getAxisEndPoint(index, pointRadius)
|
||||
})
|
||||
})
|
||||
|
||||
const userDataPoints = computed(() => {
|
||||
return userDataCoordArray.value.map((point) => `${point.x},${point.y}`).join(' ')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.radar-legend {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 6px 8px;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
background-color: rgba(33, 150, 243, 0.12) !important;
|
||||
border: 1px dashed #2196f3;
|
||||
}
|
||||
|
||||
.radar-svg text {
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<v-card
|
||||
class="rounded-xl elevation-2 d-flex align-center justify-between pa-4 my-4"
|
||||
color="surface"
|
||||
:to="to"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" :color="iconColor" size="48">
|
||||
<v-icon :icon="icon" />
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">{{ title }}</div>
|
||||
|
||||
<div class="text-caption text-grey">{{ description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
to: string
|
||||
icon: string
|
||||
iconColor: string
|
||||
title: string
|
||||
description: string
|
||||
}>()
|
||||
</script>
|
||||
@@ -21,18 +21,6 @@
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
@@ -189,7 +177,7 @@
|
||||
>
|
||||
<v-btn to="/" value="home">
|
||||
<v-icon :color="getBottomNavIconColor('home')">mdi-view-dashboard</v-icon>
|
||||
<span class="bottom-nav-text">主控台</span>
|
||||
<span class="bottom-nav-text">首頁</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn to="/self-assessment" value="self-assessment">
|
||||
@@ -197,17 +185,17 @@
|
||||
mdi-clipboard-text-search
|
||||
</v-icon>
|
||||
|
||||
<span class="bottom-nav-text">線上自評</span>
|
||||
<span class="bottom-nav-text">自評</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="store.isLoggedIn" to="/appointment" value="appointment">
|
||||
<v-icon :color="getBottomNavIconColor('appointment')">mdi-calendar-check</v-icon>
|
||||
<span class="bottom-nav-text">活動預約</span>
|
||||
<span class="bottom-nav-text">預約</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-if="store.isLoggedIn" to="/checkin" value="checkin">
|
||||
<v-icon :color="getBottomNavIconColor('checkin')">mdi-qrcode-scan</v-icon>
|
||||
<span class="bottom-nav-text">現場量測</span>
|
||||
<span class="bottom-nav-text">量測</span>
|
||||
</v-btn>
|
||||
</v-bottom-navigation>
|
||||
|
||||
@@ -307,20 +295,20 @@ 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) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import App from './App.vue'
|
||||
|
||||
// Styles
|
||||
import '@/styles/fonts.css'
|
||||
import 'unfonts.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="text-subtitle-1 text-grey font-weight-bold">目前沒有相關活動</div>
|
||||
</div>
|
||||
|
||||
<v-row v-else dense>
|
||||
<v-row v-else>
|
||||
<v-col v-for="apt in filteredAppointments" :key="apt.id" class="mb-4" cols="12">
|
||||
<v-card class="rounded-xl elevation-3 border-accent-light" color="surface">
|
||||
<!-- 卡片頂部狀態條 -->
|
||||
@@ -63,7 +63,12 @@
|
||||
<v-icon class="mr-2 mt-0.5" color="primary" icon="mdi-human-capacity" size="18" />
|
||||
|
||||
<div class="text-body-2 text-grey-darken-3">
|
||||
剩餘名額:<span class="font-weight-bold" :class="apt.slots < 10 ? 'text-error' : 'text-success'">{{ apt.slots }}</span> 位
|
||||
剩餘名額:<span
|
||||
class="font-weight-bold"
|
||||
:class="apt.slots < 10 ? 'text-error' : 'text-success'"
|
||||
>{{ apt.slots }}</span
|
||||
>
|
||||
位
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +111,7 @@
|
||||
|
||||
<!-- 已報名狀態 -->
|
||||
<template v-else-if="apt.status === '已報名'">
|
||||
<v-row class="w-100" dense>
|
||||
<v-row class="w-100">
|
||||
<v-col cols="6">
|
||||
<v-btn
|
||||
block
|
||||
@@ -166,7 +171,9 @@
|
||||
<p class="text-caption text-grey mb-4">請在現場合對處出示此條碼進行報到</p>
|
||||
|
||||
<!-- 模擬 QR Code SVG,更高級精緻 -->
|
||||
<div class="qrcode-wrapper mx-auto mb-4 elevation-3 rounded-lg pa-3 bg-white border d-flex justify-center align-center">
|
||||
<div
|
||||
class="qrcode-wrapper mx-auto mb-4 elevation-3 rounded-lg pa-3 bg-white border d-flex justify-center align-center"
|
||||
>
|
||||
<svg class="qrcode-svg" height="180" viewBox="0 0 100 100" width="180">
|
||||
<!-- 四個角定位框 -->
|
||||
<path d="M 0,0 H 25 V 8 H 8 V 25 H 0 Z" fill="#1D7063" />
|
||||
@@ -174,142 +181,40 @@
|
||||
<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" />
|
||||
@@ -335,7 +240,12 @@
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4 justify-center">
|
||||
<v-btn class="rounded-lg px-6" color="primary" variant="flat" @click="qrcodeDialog = false">
|
||||
<v-btn
|
||||
class="rounded-lg px-6"
|
||||
color="primary"
|
||||
variant="flat"
|
||||
@click="qrcodeDialog = false"
|
||||
>
|
||||
關閉條碼
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@@ -358,20 +268,24 @@
|
||||
|
||||
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) {
|
||||
switch (status) {
|
||||
case '未報名': { return 'grey-darken-1'
|
||||
case '未報名': {
|
||||
return 'grey-darken-1'
|
||||
}
|
||||
case '已報名': { return 'secondary'
|
||||
case '已報名': {
|
||||
return 'secondary'
|
||||
}
|
||||
case '已報到': { return 'success'
|
||||
case '已報到': {
|
||||
return 'success'
|
||||
}
|
||||
default: { return 'grey'
|
||||
default: {
|
||||
return 'grey'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,8 +343,14 @@
|
||||
}
|
||||
|
||||
@keyframes scan {
|
||||
0% { top: 0%; }
|
||||
50% { top: 100%; }
|
||||
100% { top: 0%; }
|
||||
0% {
|
||||
top: 0%;
|
||||
}
|
||||
50% {
|
||||
top: 100%;
|
||||
}
|
||||
100% {
|
||||
top: 0%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
</div>
|
||||
|
||||
<!-- 報到活動資訊 -->
|
||||
<v-card v-if="activeAppointment" class="rounded-xl elevation-3 border-accent-light mb-6" color="surface">
|
||||
<v-card
|
||||
v-if="activeAppointment"
|
||||
class="rounded-xl elevation-3 border-accent-light mb-6"
|
||||
color="surface"
|
||||
>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-between mb-3">
|
||||
<span class="text-subtitle-2 font-weight-bold text-primary">
|
||||
@@ -32,7 +36,9 @@
|
||||
<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
|
||||
@@ -51,12 +57,19 @@
|
||||
<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>
|
||||
<p class="text-body-2 text-grey mb-4">
|
||||
請先前往「活動預約」頁面,出示您的報到條碼給現場志工進行核對。
|
||||
</p>
|
||||
<v-btn class="rounded-lg" color="primary" to="/appointment">前往查看預約活動</v-btn>
|
||||
</v-card>
|
||||
|
||||
<!-- 快捷測試功能 (對 Demo 非常友善) -->
|
||||
<v-card v-if="activeAppointment && completedCount < totalCount" class="rounded-xl mb-6 border-accent-light bg-teal-lighten-5" color="primary" variant="outlined">
|
||||
<v-card
|
||||
v-if="activeAppointment && completedCount < totalCount"
|
||||
class="rounded-xl mb-6 border-accent-light bg-teal-lighten-5"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="text-subtitle-2 font-weight-bold text-primary">
|
||||
@@ -64,10 +77,17 @@
|
||||
高齡友善測試輔助
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey-darken-1">一鍵填入所有剩餘關卡數值,便於檢視報告</div>
|
||||
<div class="text-caption text-grey-darken-1">
|
||||
一鍵填入所有剩餘關卡數值,便於檢視報告
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-btn class="rounded-lg font-weight-bold text-white" color="secondary" size="small" @click="simulateAllMeasurements">
|
||||
<v-btn
|
||||
class="rounded-lg font-weight-bold text-white"
|
||||
color="secondary"
|
||||
size="small"
|
||||
@click="simulateAllMeasurements"
|
||||
>
|
||||
一鍵模擬完成
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
@@ -77,7 +97,7 @@
|
||||
<div v-if="activeAppointment" class="mb-6">
|
||||
<h3 class="text-subtitle-2 font-weight-black text-primary mb-3">關卡列表</h3>
|
||||
|
||||
<v-row dense>
|
||||
<v-row>
|
||||
<v-col v-for="(station, key) in store.measurements" :key="key" class="mb-2" cols="12">
|
||||
<v-card
|
||||
class="rounded-xl elevation-2 cursor-pointer transition-card"
|
||||
@@ -91,22 +111,28 @@
|
||||
:color="station.completed ? 'success' : 'grey-lighten-2'"
|
||||
size="40"
|
||||
>
|
||||
<v-icon :icon="station.completed ? 'mdi-check' : getStationIcon(key)" size="20" />
|
||||
<v-icon
|
||||
:icon="station.completed ? 'mdi-check' : getStationIcon(key)"
|
||||
size="20"
|
||||
/>
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-body-1" :class="station.completed ? 'text-success' : 'text-grey-darken-4'">
|
||||
<div
|
||||
class="font-weight-bold text-body-1"
|
||||
:class="station.completed ? 'text-success' : 'text-grey-darken-4'"
|
||||
>
|
||||
{{ station.name }}
|
||||
</div>
|
||||
<!-- 已完成,顯示數值 -->
|
||||
<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>
|
||||
<div v-else class="text-caption text-grey-darken-1">等待現場檢測員登錄</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +199,7 @@
|
||||
|
||||
// 尋找已報到的活動
|
||||
const activeAppointment = computed(() => {
|
||||
return store.appointments.find(a => a.status === '已報到')
|
||||
return store.appointments.find((a) => a.status === '已報到')
|
||||
})
|
||||
|
||||
const completedCount = computed(() => store.completedStationsCount)
|
||||
@@ -182,29 +208,41 @@
|
||||
|
||||
function getStationIcon(code: string) {
|
||||
switch (code) {
|
||||
case 'BloodPressure': { return 'mdi-heart-flash'
|
||||
case 'BloodPressure': {
|
||||
return 'mdi-heart-flash'
|
||||
}
|
||||
case 'BodyFat': { return 'mdi-scale-bathroom'
|
||||
case 'BodyFat': {
|
||||
return 'mdi-scale-bathroom'
|
||||
}
|
||||
case 'GripStrength': { return 'mdi-hand-back-left'
|
||||
case 'GripStrength': {
|
||||
return 'mdi-hand-back-left'
|
||||
}
|
||||
case 'Sit5x': { return 'mdi-human-male-height'
|
||||
case 'Sit5x': {
|
||||
return 'mdi-human-male-height'
|
||||
}
|
||||
case 'Sit30s': { return 'mdi-chair-school'
|
||||
case 'Sit30s': {
|
||||
return 'mdi-chair-school'
|
||||
}
|
||||
case 'StepTest': { return 'mdi-run-fast'
|
||||
case 'StepTest': {
|
||||
return 'mdi-run-fast'
|
||||
}
|
||||
case 'ArmTest': { return 'mdi-arm-flex'
|
||||
case 'ArmTest': {
|
||||
return 'mdi-arm-flex'
|
||||
}
|
||||
case 'BackTest': { return 'mdi-human-stretching'
|
||||
case 'BackTest': {
|
||||
return 'mdi-human-stretching'
|
||||
}
|
||||
case 'SitReachTest': { return 'mdi-human-stretching'
|
||||
case 'SitReachTest': {
|
||||
return 'mdi-human-stretching'
|
||||
}
|
||||
case 'UpGo8ft': { return 'mdi-navigation'
|
||||
case 'UpGo8ft': {
|
||||
return 'mdi-navigation'
|
||||
}
|
||||
case 'OneLegTest': { return 'mdi-scale-balance'
|
||||
case 'OneLegTest': {
|
||||
return 'mdi-scale-balance'
|
||||
}
|
||||
default: { return 'mdi-checkbox-blank-circle-outline'
|
||||
default: {
|
||||
return 'mdi-checkbox-blank-circle-outline'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<v-form @submit.prevent="handleSave">
|
||||
<h3 class="text-subtitle-2 font-weight-bold text-grey-darken-3 mb-3">數據錄入:</h3>
|
||||
|
||||
<v-row dense>
|
||||
<v-row>
|
||||
<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>
|
||||
|
||||
@@ -3,341 +3,105 @@
|
||||
<template #main>
|
||||
<template v-if="store.isLoggedIn">
|
||||
<!-- 頂部:我的健康力護照卡片 (Taipei Masters Hub) -->
|
||||
<v-card
|
||||
class="passport-card rounded-xl text-white pa-5 elevation-8 mb-6 position-relative overflow-hidden"
|
||||
>
|
||||
<!-- 背景裝飾圓圈 -->
|
||||
<div class="bg-circle-1" />
|
||||
<div class="bg-circle-2" />
|
||||
|
||||
<div class="d-flex justify-space-between align-center mb-6 z-index-1 position-relative">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 border-white" color="accent" size="44">
|
||||
<span class="text-white font-weight-black text-subtitle-1">{{
|
||||
store.userProfile.name.substring(1)
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<h2 class="text-subtitle-1 font-weight-black mb-0">
|
||||
{{ store.userProfile.name }} 先生
|
||||
</h2>
|
||||
|
||||
<span class="text-caption text-white-70">更新於今日 {{ todayTimeString }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<v-card class="border-primary border-xl rounded-xl" variant="flat">
|
||||
<v-row class="mt-4" no-gutters>
|
||||
<v-col cols="8">
|
||||
<v-card-title> {{ store.userProfile.name }} 先生 </v-card-title>
|
||||
<v-card-subtitle> 資料更新於 {{ todayTimeString }} </v-card-subtitle>
|
||||
</v-col>
|
||||
|
||||
<v-col class="d-flex justify-center" cols="4">
|
||||
<!-- 健康力分數大圓盤 -->
|
||||
<div class="score-disk d-flex flex-column justify-center align-center">
|
||||
<span class="score-val">{{ store.healthScore }}</span>
|
||||
<span class="score-val">{{ displayedHealthScore }}</span>
|
||||
<span class="score-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能儲備與運動處方簡介 -->
|
||||
<div class="z-index-1 position-relative mb-4">
|
||||
<div class="d-inline-flex align-center bg-white-20 px-3 py-1 rounded-pill mb-3">
|
||||
<v-icon class="mr-1 text-accent-light" icon="mdi-heart-pulse" size="16" />
|
||||
<span class="text-caption font-weight-bold">功能儲備:{{ functionalReserve }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-body-2 font-weight-medium bg-white-10 rounded-lg pa-3">
|
||||
<div class="font-weight-black mb-1 text-accent-light d-flex align-center">
|
||||
<v-icon class="mr-1" icon="mdi-notebook-check-outline" size="16" />
|
||||
本週處方建議:
|
||||
</div>
|
||||
{{ currentWeeklyPrescription }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="bg-white-20 my-3 z-index-1 position-relative" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 四項核心能力數值簡覽 -->
|
||||
<v-row class="z-index-1 position-relative text-center" dense>
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">肌力</div>
|
||||
<v-row class="mt-4" no-gutters>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">肌力</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.strength }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">平衡</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">平衡</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.balance }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">敏捷</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">敏捷</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.agility }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">心肺</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">心肺</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.cardio }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 功能儲備與運動處方簡介 -->
|
||||
|
||||
<v-card-text class="bg-primary">
|
||||
<div class="font-weight-bold text-accent-light">
|
||||
<v-icon icon="mdi-notebook-check-outline" size="16" />
|
||||
運動處方建議:
|
||||
</div>
|
||||
|
||||
<p class="text-white mt-2">
|
||||
{{ currentWeeklyPrescription }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 中間:功能指標與五維雷達圖 -->
|
||||
<v-card class="rounded-xl elevation-3 border-accent-light mb-6 pa-4" color="surface">
|
||||
<h3 class="text-subtitle-1 font-weight-black text-primary mb-2 d-flex align-center">
|
||||
<v-icon class="mr-2" color="secondary" icon="mdi-radar" />
|
||||
個人五維健康力指標
|
||||
</h3>
|
||||
<v-card class="border-xl border-primary rounded-xl mt-4" variant="flat">
|
||||
<v-card-title> 個人五維健康力指標 </v-card-title>
|
||||
|
||||
<p class="text-caption text-grey mb-4">
|
||||
綜合 11 項現場普測數值計算得出,藍色為同齡正常參考值區間。
|
||||
</p>
|
||||
<v-card-subtitle>
|
||||
綜合 11 項現場普測數值計算得出,<br />藍色為同齡正常參考值區間。
|
||||
</v-card-subtitle>
|
||||
|
||||
<!-- 純 SVG 雷達圖,不依賴任何外部圖表庫,保證完美渲染 -->
|
||||
<div class="d-flex justify-center my-2 position-relative">
|
||||
<svg class="radar-svg" height="260" viewBox="0 0 120 120" width="260">
|
||||
<!-- 5 個同心五邊形網格 (背景線) -->
|
||||
<polygon
|
||||
v-for="grid in [20, 40, 60, 80, 100]"
|
||||
:key="grid"
|
||||
fill="none"
|
||||
:points="getPolygonPoints(grid)"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
/>
|
||||
|
||||
<!-- 五個維度軸線 -->
|
||||
<line
|
||||
v-for="i in 5"
|
||||
:key="`axis-${i}`"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
x1="60"
|
||||
:x2="getAxisEndPoint(i - 1).x"
|
||||
y1="60"
|
||||
:y2="getAxisEndPoint(i - 1).y"
|
||||
/>
|
||||
|
||||
<!-- 同齡參考區間 (同齡常模基準:設為 65 分) -->
|
||||
<polygon
|
||||
fill="rgba(33, 150, 243, 0.12)"
|
||||
:points="getPolygonPoints(65)"
|
||||
stroke="#2196F3"
|
||||
stroke-dasharray="1 1"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<!-- 使用者真實數據填充面 -->
|
||||
<polygon
|
||||
fill="rgba(71, 194, 166, 0.35)"
|
||||
:points="userDataPoints"
|
||||
stroke="#1D7063"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
|
||||
<!-- 使用者數據頂點標記圓圈 -->
|
||||
<circle
|
||||
v-for="(pt, idx) in userDataCoordArray"
|
||||
:key="`dot-${idx}`"
|
||||
:cx="pt.x"
|
||||
:cy="pt.y"
|
||||
fill="#E66926"
|
||||
r="1.5"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<!-- 頂點文字標記 (Cardio, Strength, Balance, Flexibility, Agility) -->
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
x="60"
|
||||
y="8"
|
||||
>
|
||||
心肺 ({{ store.radarData.cardio }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="start"
|
||||
x="110"
|
||||
y="44"
|
||||
>
|
||||
肌力 ({{ store.radarData.strength }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="start"
|
||||
x="94"
|
||||
y="104"
|
||||
>
|
||||
平衡 ({{ store.radarData.balance }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="end"
|
||||
x="26"
|
||||
y="104"
|
||||
>
|
||||
柔軟 ({{ store.radarData.flexibility }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="end"
|
||||
x="10"
|
||||
y="44"
|
||||
>
|
||||
敏捷 ({{ store.radarData.agility }})
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<!-- 圖例標示 -->
|
||||
<div class="radar-legend d-flex flex-column">
|
||||
<div class="d-flex align-center text-caption mb-1">
|
||||
<span class="legend-color bg-primary mr-1" />
|
||||
<span>我的指標</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center text-caption">
|
||||
<span class="legend-color bg-blue mr-1 border-dashed" />
|
||||
<span>同齡參考</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HealthRadarChart :radar-data="store.radarData" />
|
||||
</v-card>
|
||||
|
||||
<!-- 下一步行動引導 (根據量測進度動態顯示) -->
|
||||
<h3 class="text-subtitle-2 font-weight-black text-primary mb-3">下一步行動推薦</h3>
|
||||
<ModuleRouterCard
|
||||
description="完成線上自評,取得初步風險分級。"
|
||||
icon="mdi-clipboard-text-search"
|
||||
icon-color="accent"
|
||||
title="自評"
|
||||
to="/self-assessment"
|
||||
/>
|
||||
|
||||
<!-- 情況 1:普測量測正在進行中 -->
|
||||
<v-card
|
||||
v-if="isIncompleteMeasurement"
|
||||
class="rounded-xl elevation-3 mb-4 transition-card text-left"
|
||||
color="surface"
|
||||
<ModuleRouterCard
|
||||
description="完成數值紀錄,建立個人功能基線。"
|
||||
icon="mdi-run-fast"
|
||||
icon-color="accent"
|
||||
title="量測"
|
||||
to="/checkin"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="secondary" size="48">
|
||||
<v-icon icon="mdi-run-fast" />
|
||||
</v-avatar>
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
繼續現場體能普測
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">
|
||||
目前已完成 {{ completedCount }} / 11 站,繼續完成登錄。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 情況 2:量測全部完成,提醒可以看運動處方任務 -->
|
||||
<v-card
|
||||
v-else
|
||||
class="rounded-xl elevation-3 mb-4 transition-card text-left"
|
||||
color="surface"
|
||||
@click="prescriptionDialog = true"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="secondary" size="48">
|
||||
<v-icon icon="mdi-calendar-multiselect" />
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
執行本週運動處方課程
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">
|
||||
依普測結果產生,肌力 2 次、心肺 2 次、平衡 3 次。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 普測預約快捷卡片 -->
|
||||
<v-card
|
||||
class="rounded-xl elevation-3 mb-6 transition-card text-left"
|
||||
color="surface"
|
||||
<ModuleRouterCard
|
||||
description="預訂首次量測或回測,追蹤健康狀態。"
|
||||
icon="mdi-calendar-clock"
|
||||
icon-color="accent"
|
||||
title="預約"
|
||||
to="/appointment"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="primary" size="48">
|
||||
<v-icon icon="mdi-calendar-clock" />
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
預約 30 秒坐站與平衡回測
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">4 週 / 12 週定期回測,動態追蹤體能進步狀態。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 最近一次量測結果摘要 -->
|
||||
<v-card class="rounded-xl elevation-3 mb-8 pa-4" color="surface">
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<h3 class="text-subtitle-1 font-weight-black text-primary d-flex align-center">
|
||||
<v-icon class="mr-2" color="secondary" icon="mdi-file-eye-outline" />
|
||||
最近量測數值摘要
|
||||
</h3>
|
||||
|
||||
<v-btn
|
||||
class="font-weight-bold px-0"
|
||||
color="primary"
|
||||
size="small"
|
||||
to="/checkin"
|
||||
variant="text"
|
||||
>查看全部</v-btn>
|
||||
</div>
|
||||
|
||||
<v-row dense>
|
||||
<v-col v-for="item in recentItems" :key="item.name" class="mb-2" cols="6">
|
||||
<div class="bg-grey-lighten-4 rounded-lg pa-3 h-100">
|
||||
<div class="text-caption text-grey-darken-1 mb-1">{{ item.name }}</div>
|
||||
|
||||
<div class="text-body-1 font-weight-black text-grey-darken-4">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
/>
|
||||
|
||||
<!-- 運動處方對話框 -->
|
||||
<v-dialog v-model="prescriptionDialog" max-width="500">
|
||||
@@ -417,17 +181,15 @@
|
||||
<template v-else>
|
||||
<div class="text-center mb-6">
|
||||
<v-avatar class="elevation-3 mb-4 border-accent-light" color="primary" size="72">
|
||||
<v-icon color="accent" icon="mdi-heart-pulse" size="42" />
|
||||
<v-icon color="red-accent-1" icon="mdi-heart-pulse" size="42" />
|
||||
</v-avatar>
|
||||
|
||||
<h1 class="text-h5 font-weight-black text-primary mb-2">運動玩轉健康力</h1>
|
||||
|
||||
<p class="text-body-2 text-grey-darken-1 mb-0">
|
||||
不登入也可以先完成線上健康自評,登入後可預約普測與查看量測紀錄。
|
||||
<p class="text-body-medium text-grey-darken-1 mb-0">
|
||||
不登入也可以先完成線上健康自評,<br />登入後可預約普測與查看量測紀錄。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-card class="rounded-xl elevation-3 border-accent-light mb-4" color="surface">
|
||||
<v-card class="rounded-xl elevation-2 border-accent-light mb-4" color="surface">
|
||||
<v-card-text class="pa-5">
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-avatar class="mr-3 text-white" color="secondary" size="48">
|
||||
@@ -442,13 +204,13 @@
|
||||
|
||||
<v-btn
|
||||
block
|
||||
class="rounded-lg font-weight-bold mb-3"
|
||||
color="primary"
|
||||
prepend-icon="mdi-arrow-right-circle"
|
||||
class="rounded-lg font-weight-bold elevation-1 mb-3"
|
||||
color="secondary"
|
||||
prepend-icon="mdi-clipboard-text-search"
|
||||
to="/self-assessment"
|
||||
variant="flat"
|
||||
>
|
||||
開始健康自評
|
||||
開始自評
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
@@ -459,7 +221,7 @@
|
||||
to="/login"
|
||||
variant="outlined"
|
||||
>
|
||||
登入健康平台
|
||||
登入平台
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -483,116 +245,81 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import HealthRadarChart from '@/components/HealthRadarChart.vue'
|
||||
import Layout from '@/components/layout/DefaultLayout.vue'
|
||||
import ModuleRouterCard from '@/components/ModuleRouterCard.vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const store = useAppStore()
|
||||
const prescriptionDialog = ref(false)
|
||||
const displayedHealthScore = ref(1)
|
||||
let healthScoreTimer: number | undefined
|
||||
|
||||
function animateHealthScore (targetScore: number) {
|
||||
if (healthScoreTimer !== undefined) {
|
||||
window.clearInterval(healthScoreTimer)
|
||||
}
|
||||
|
||||
if (targetScore <= 1) {
|
||||
displayedHealthScore.value = targetScore
|
||||
return
|
||||
}
|
||||
|
||||
displayedHealthScore.value = 1
|
||||
|
||||
const duration = 900
|
||||
const stepDuration = Math.max(12, Math.floor(duration / targetScore))
|
||||
|
||||
healthScoreTimer = window.setInterval(() => {
|
||||
if (displayedHealthScore.value >= targetScore) {
|
||||
window.clearInterval(healthScoreTimer)
|
||||
healthScoreTimer = undefined
|
||||
return
|
||||
}
|
||||
|
||||
displayedHealthScore.value += 1
|
||||
}, stepDuration)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
animateHealthScore(store.healthScore)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (healthScoreTimer !== undefined) {
|
||||
window.clearInterval(healthScoreTimer)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => store.healthScore,
|
||||
score => {
|
||||
animateHealthScore(score)
|
||||
},
|
||||
)
|
||||
|
||||
const todayTimeString = computed(() => {
|
||||
const now = new Date()
|
||||
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||||
})
|
||||
const date = [
|
||||
String(now.getMonth() + 1).padStart(2, '0'),
|
||||
String(now.getDate()).padStart(2, '0'),
|
||||
].join('/')
|
||||
const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||||
|
||||
// 功能儲備評估
|
||||
const functionalReserve = computed(() => {
|
||||
const score = store.healthScore
|
||||
if (score >= 80) return '良好 (無衰弱風險)'
|
||||
if (score >= 65) return '正常 (需持續運動維持)'
|
||||
return '待加強 (有衰弱與跌倒風險)'
|
||||
return `${date} ${time}`
|
||||
})
|
||||
|
||||
// 每週處方文字
|
||||
const currentWeeklyPrescription = computed(() => {
|
||||
if (store.healthScore >= 80) {
|
||||
return '本週處方:1次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
return '1次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
} else if (store.healthScore >= 65) {
|
||||
return '本週處方:2次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
return '2次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
} else {
|
||||
return '本週處方:3次防跌平衡引導 + 每日15分鐘扶椅坐站與慢走'
|
||||
return '3次防跌平衡引導 + 每日15分鐘扶椅坐站與慢走'
|
||||
}
|
||||
})
|
||||
|
||||
// 檢測是否還有未完成的測量站
|
||||
const isIncompleteMeasurement = computed(() => {
|
||||
return store.completedStationsCount < store.totalStationsCount
|
||||
})
|
||||
|
||||
const completedCount = computed(() => store.completedStationsCount)
|
||||
|
||||
// 最近一次量測結果摘要列表
|
||||
const recentItems = computed(() => {
|
||||
const m = store.measurements
|
||||
const items = []
|
||||
|
||||
if (m.BloodPressure.completed) {
|
||||
items.push({
|
||||
name: '血壓量測',
|
||||
value: `${m.BloodPressure.values.systolic}/${m.BloodPressure.values.diastolic} mmHg`,
|
||||
})
|
||||
}
|
||||
if (m.BodyFat.completed) {
|
||||
items.push({ name: '體組成 BMI', value: `${m.BodyFat.values.bmi}` })
|
||||
}
|
||||
if (m.GripStrength.completed) {
|
||||
items.push({ name: '右手最大握力', value: `${m.GripStrength.values.rightHand} kg` })
|
||||
} else {
|
||||
items.push({ name: '左右握力', value: '未測量' })
|
||||
}
|
||||
if (m.OneLegTest.completed) {
|
||||
items.push({
|
||||
name: '單腳站立平衡',
|
||||
value: `${Math.max(m.OneLegTest.values.leftSeconds, m.OneLegTest.values.rightSeconds)} 秒`,
|
||||
})
|
||||
} else {
|
||||
items.push({ name: '開眼單足立', value: '未測量' })
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// --- SVG 雷達圖點計算邏輯 ---
|
||||
const cx = 60
|
||||
const cy = 60
|
||||
const r = 45
|
||||
|
||||
// 計算 5 角多邊形的頂點
|
||||
// 頂點順序: i=0(心肺), i=1(肌力), i=2(平衡), i=3(柔軟), i=4(敏捷)
|
||||
function getAxisEndPoint (i: number, radius = r) {
|
||||
const angle = -Math.PI / 2 + (i * 2 * Math.PI) / 5
|
||||
return {
|
||||
x: cx + radius * Math.cos(angle),
|
||||
y: cy + radius * Math.sin(angle),
|
||||
}
|
||||
}
|
||||
|
||||
// 根據百分比半徑繪製背景網格五邊形
|
||||
function getPolygonPoints (percent: number) {
|
||||
const radius = (percent / 100) * r
|
||||
const pts = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const pt = getAxisEndPoint(i, radius)
|
||||
pts.push(`${pt.x},${pt.y}`)
|
||||
}
|
||||
return pts.join(' ')
|
||||
}
|
||||
|
||||
// 使用者五維數據對應的雷達圖頂點座標
|
||||
const userDataCoordArray = computed(() => {
|
||||
const data = store.radarData
|
||||
// 順序與頂點對應:
|
||||
// 0: cardio (心肺), 1: strength (肌力), 2: balance (平衡), 3: flexibility (柔軟), 4: agility (敏捷)
|
||||
const values = [data.cardio, data.strength, data.balance, data.flexibility, data.agility]
|
||||
return values.map((val, i) => {
|
||||
const radius = (Math.max(5, Math.min(100, val)) / 100) * r
|
||||
return getAxisEndPoint(i, radius)
|
||||
})
|
||||
})
|
||||
|
||||
const userDataPoints = computed(() => {
|
||||
return userDataCoordArray.value.map(pt => `${pt.x},${pt.y}`).join(' ')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -681,31 +408,4 @@ const userDataPoints = computed(() => {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 雷達圖圖例 */
|
||||
.radar-legend {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 6px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
border: 1px dashed #2196f3;
|
||||
background-color: rgba(33, 150, 243, 0.12) !important;
|
||||
}
|
||||
|
||||
.radar-svg text {
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -50,13 +50,22 @@
|
||||
class="glow-sweep-button rounded-lg font-weight-bold elevation-2 mb-4"
|
||||
color="secondary"
|
||||
:loading="loading"
|
||||
size="large"
|
||||
type="submit"
|
||||
>
|
||||
登入健康平台
|
||||
</v-btn>
|
||||
</v-form>
|
||||
|
||||
<v-btn
|
||||
block
|
||||
class="rounded-lg font-weight-bold"
|
||||
color="primary"
|
||||
prepend-icon="mdi-clipboard-text-search"
|
||||
to="/self-assessment"
|
||||
variant="outlined"
|
||||
>
|
||||
不登入,先做線上自評
|
||||
</v-btn>
|
||||
</v-form>
|
||||
<!-- <div class="d-flex align-center justify-center my-4">
|
||||
<v-divider class="mr-3" />
|
||||
<span class="text-caption text-grey font-weight-bold">或使用以下方式快捷登入</span>
|
||||
@@ -155,7 +164,7 @@ function _handleLineLogin () {
|
||||
rgba(255, 255, 255, 0.15) 65%,
|
||||
transparent 100%
|
||||
);
|
||||
content: "";
|
||||
content: '';
|
||||
transform: rotate(18deg);
|
||||
animation: glow-sweep 2.4s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 進度指示器 -->
|
||||
<v-row class="mb-6 justify-center" dense>
|
||||
<v-row class="mb-6 justify-center">
|
||||
<v-col v-for="(stepName, idx) in stepNames" :key="idx" class="text-center" cols="3">
|
||||
<v-avatar
|
||||
class="text-white font-weight-bold mb-1 elevation-1"
|
||||
:color="currentStep > idx ? 'primary' : currentStep === idx ? 'secondary' : 'grey-lighten-2'"
|
||||
:color="
|
||||
currentStep > idx ? 'primary' : currentStep === idx ? 'secondary' : 'grey-lighten-2'
|
||||
"
|
||||
size="28"
|
||||
>
|
||||
<v-icon v-if="currentStep > idx" icon="mdi-check" size="14" />
|
||||
@@ -20,7 +22,13 @@
|
||||
|
||||
<div
|
||||
class="text-caption font-weight-bold"
|
||||
:class="currentStep === idx ? 'text-secondary' : currentStep > idx ? 'text-primary' : 'text-grey'"
|
||||
:class="
|
||||
currentStep === idx
|
||||
? 'text-secondary'
|
||||
: currentStep > idx
|
||||
? 'text-primary'
|
||||
: 'text-grey'
|
||||
"
|
||||
>
|
||||
{{ stepName }}
|
||||
</div>
|
||||
@@ -37,7 +45,7 @@
|
||||
基本資料確認
|
||||
</h2>
|
||||
|
||||
<v-row dense>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="profileForm.name"
|
||||
@@ -120,7 +128,9 @@
|
||||
活動習慣與想要改善的能力
|
||||
</h2>
|
||||
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">1. 最近 7 天,您每週進行大運動幾次?</p>
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
|
||||
1. 最近 7 天,您每週進行大運動幾次?
|
||||
</p>
|
||||
|
||||
<v-radio-group v-model="assessmentForm.exerciseFrequency" class="mb-4" color="primary">
|
||||
<v-card
|
||||
@@ -151,18 +161,26 @@
|
||||
</v-card>
|
||||
</v-radio-group>
|
||||
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">2. 您目前最想改善的身體功能? (可複選)</p>
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
|
||||
2. 您目前最想改善的身體功能? (可複選)
|
||||
</p>
|
||||
|
||||
<v-row dense>
|
||||
<v-row>
|
||||
<v-col v-for="imp in improvementsOptions" :key="imp" cols="6">
|
||||
<v-card
|
||||
class="rounded-lg py-2 px-1 text-center cursor-pointer transition-card"
|
||||
:class="{ 'bg-teal-lighten-5': assessmentForm.wantedImprovements.includes(imp) }"
|
||||
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey-lighten-1'"
|
||||
:color="
|
||||
assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey-lighten-1'
|
||||
"
|
||||
variant="outlined"
|
||||
@click="toggleImprovement(imp)"
|
||||
>
|
||||
<v-icon class="mb-1" :color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'" :icon="getImprovementIcon(imp)" />
|
||||
<v-icon
|
||||
class="mb-1"
|
||||
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'"
|
||||
:icon="getImprovementIcon(imp)"
|
||||
/>
|
||||
<div class="text-caption font-weight-bold">{{ imp }}</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -176,14 +194,18 @@
|
||||
身體限制與慢性病史
|
||||
</h2>
|
||||
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">1. 過去一年是否有過跌倒史?</p>
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
|
||||
1. 過去一年是否有過跌倒史?
|
||||
</p>
|
||||
|
||||
<v-radio-group v-model="assessmentForm.hasFalls" class="mb-4" color="primary" inline>
|
||||
<v-radio label="無跌倒" :value="false" />
|
||||
<v-radio label="有跌倒過" :value="true" />
|
||||
</v-radio-group>
|
||||
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">2. 目前身體關節疼痛狀況?</p>
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
|
||||
2. 目前身體關節疼痛狀況?
|
||||
</p>
|
||||
|
||||
<v-select
|
||||
v-model="assessmentForm.painLevel"
|
||||
@@ -194,9 +216,11 @@
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">3. 您有哪些慢性疾病? (可複選)</p>
|
||||
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
|
||||
3. 您有哪些慢性疾病? (可複選)
|
||||
</p>
|
||||
|
||||
<v-row class="mb-4" dense>
|
||||
<v-row class="mb-4">
|
||||
<v-col v-for="disease in diseaseOptions" :key="disease" cols="6">
|
||||
<v-checkbox
|
||||
v-model="assessmentForm.chronicDiseases"
|
||||
@@ -221,7 +245,10 @@
|
||||
@keyup.enter="addMedication"
|
||||
/>
|
||||
|
||||
<div v-if="assessmentForm.medicationReminders.length > 0" class="d-flex flex-wrap gap-2 mt-2">
|
||||
<div
|
||||
v-if="assessmentForm.medicationReminders.length > 0"
|
||||
class="d-flex flex-wrap gap-2 mt-2"
|
||||
>
|
||||
<v-chip
|
||||
v-for="(med, idx) in assessmentForm.medicationReminders"
|
||||
:key="idx"
|
||||
@@ -241,7 +268,10 @@
|
||||
<v-icon color="white" :icon="getResultIcon(resultLevel)" size="40" />
|
||||
</v-avatar>
|
||||
|
||||
<h2 class="text-h5 font-weight-bold mb-1" :class="`text-${getResultColor(resultLevel)}`">
|
||||
<h2
|
||||
class="text-h5 font-weight-bold mb-1"
|
||||
:class="`text-${getResultColor(resultLevel)}`"
|
||||
>
|
||||
自評分級:{{ resultLevel }}
|
||||
</h2>
|
||||
|
||||
@@ -254,7 +284,8 @@
|
||||
</div>
|
||||
|
||||
<p v-if="resultLevel === '低風險'" class="text-body-2 text-grey-darken-3 mb-2">
|
||||
您的身體功能與日常活動習慣良好。建議您每週繼續保持 3 次以上的運動,並可以預約普測活動以獲得精準的五維健康指標與運動處方。
|
||||
您的身體功能與日常活動習慣良好。建議您每週繼續保持 3
|
||||
次以上的運動,並可以預約普測活動以獲得精準的五維健康指標與運動處方。
|
||||
</p>
|
||||
|
||||
<p v-else-if="resultLevel === '需追蹤'" class="text-body-2 text-grey-darken-3 mb-2">
|
||||
@@ -266,7 +297,8 @@
|
||||
</p>
|
||||
|
||||
<div class="font-weight-bold text-caption text-secondary mt-3">
|
||||
* 禁忌提醒:若收縮壓 > 160mmHg、有關節強烈疼痛或暈眩時,請立即暫停所有體能檢測與訓練!
|
||||
* 禁忌提醒:若收縮壓 >
|
||||
160mmHg、有關節強烈疼痛或暈眩時,請立即暫停所有體能檢測與訓練!
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -310,13 +342,7 @@
|
||||
</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>
|
||||
@@ -373,19 +399,26 @@
|
||||
|
||||
function getImprovementIcon(imp: string) {
|
||||
switch (imp) {
|
||||
case '肌力': { return 'mdi-arm-flex'
|
||||
case '肌力': {
|
||||
return 'mdi-arm-flex'
|
||||
}
|
||||
case '平衡': { return 'mdi-scale-balance'
|
||||
case '平衡': {
|
||||
return 'mdi-scale-balance'
|
||||
}
|
||||
case '心肺': { return 'mdi-heart-pulse'
|
||||
case '心肺': {
|
||||
return 'mdi-heart-pulse'
|
||||
}
|
||||
case '柔軟度': { return 'mdi-human-stretching'
|
||||
case '柔軟度': {
|
||||
return 'mdi-human-stretching'
|
||||
}
|
||||
case '敏捷': { return 'mdi-run-fast'
|
||||
case '敏捷': {
|
||||
return 'mdi-run-fast'
|
||||
}
|
||||
case '體脂/BMI': { return 'mdi-calculator'
|
||||
case '體脂/BMI': {
|
||||
return 'mdi-calculator'
|
||||
}
|
||||
default: { return 'mdi-help-circle'
|
||||
default: {
|
||||
return 'mdi-help-circle'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import { useAppStore } from '@/stores/app'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
scrollBehavior () {
|
||||
return { left: 0, top: 0 }
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
|
||||
@@ -205,7 +205,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
const isReportGenerated = ref(false)
|
||||
|
||||
const completedStationsCount = computed(() => {
|
||||
return Object.values(measurements.value).filter((station) => station.completed).length
|
||||
return Object.values(measurements.value).filter(station => station.completed).length
|
||||
})
|
||||
|
||||
const totalStationsCount = computed(() => {
|
||||
@@ -218,7 +218,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
return 0
|
||||
}
|
||||
|
||||
const done = Object.values(measurements.value).filter((station) => station.completed).length
|
||||
const done = Object.values(measurements.value).filter(station => station.completed).length
|
||||
return Math.round((done / keys.length) * 100)
|
||||
})
|
||||
|
||||
@@ -248,9 +248,9 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
|
||||
if (
|
||||
answers.painLevel === '中度' ||
|
||||
answers.painLevel === '重度' ||
|
||||
(answers.chronicDiseases && answers.chronicDiseases.length >= 2)
|
||||
answers.painLevel === '中度'
|
||||
|| answers.painLevel === '重度'
|
||||
|| (answers.chronicDiseases && answers.chronicDiseases.length >= 2)
|
||||
) {
|
||||
level = '建議諮詢'
|
||||
}
|
||||
@@ -259,7 +259,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
|
||||
function registerAppointment (appointmentId: string) {
|
||||
const appointment = appointments.value.find((item) => item.id === appointmentId)
|
||||
const appointment = appointments.value.find(item => item.id === appointmentId)
|
||||
if (!appointment) {
|
||||
return
|
||||
}
|
||||
@@ -269,7 +269,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
|
||||
function cancelAppointment (appointmentId: string) {
|
||||
const appointment = appointments.value.find((item) => item.id === appointmentId)
|
||||
const appointment = appointments.value.find(item => item.id === appointmentId)
|
||||
if (!appointment) {
|
||||
return
|
||||
}
|
||||
@@ -279,7 +279,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
|
||||
function checkinAppointment (appointmentId: string) {
|
||||
const appointment = appointments.value.find((item) => item.id === appointmentId)
|
||||
const appointment = appointments.value.find(item => item.id === appointmentId)
|
||||
if (!appointment) {
|
||||
return
|
||||
}
|
||||
@@ -306,10 +306,10 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
let strScore = 65
|
||||
if (currentMeasurements.GripStrength.completed) {
|
||||
const avgGrip =
|
||||
((currentMeasurements.GripStrength.values.leftHand || 0) +
|
||||
(currentMeasurements.GripStrength.values.rightHand || 0)) /
|
||||
2
|
||||
const avgGrip
|
||||
= ((currentMeasurements.GripStrength.values.leftHand || 0)
|
||||
+ (currentMeasurements.GripStrength.values.rightHand || 0))
|
||||
/ 2
|
||||
const limit = userProfile.value.gender === '男' ? 30 : 20
|
||||
strScore = Math.min(95, Math.max(50, Math.round((avgGrip / limit) * 75)))
|
||||
}
|
||||
@@ -318,7 +318,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
if (currentMeasurements.OneLegTest.completed) {
|
||||
const maxSec = Math.max(
|
||||
currentMeasurements.OneLegTest.values.leftSeconds || 0,
|
||||
currentMeasurements.OneLegTest.values.rightSeconds || 0
|
||||
currentMeasurements.OneLegTest.values.rightSeconds || 0,
|
||||
)
|
||||
balScore = Math.min(98, Math.max(40, Math.round((maxSec / 30) * 90)))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@font-face {
|
||||
font-family: "LINE Seed TW";
|
||||
font-family: "line-seed";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LINE Seed TW";
|
||||
font-family: "line-seed";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LINE Seed TW";
|
||||
font-family: "line-seed";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "LINE Seed TW";
|
||||
font-family: "line-seed";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
@@ -31,12 +31,12 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: "LINE Seed TW", sans-serif;
|
||||
font-family: "line-seed", sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
.v-application {
|
||||
font-family: "LINE Seed TW", sans-serif;
|
||||
font-family: "line-seed", sans-serif;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* Configures SASS variables and Vuetify overwrites
|
||||
*/
|
||||
|
||||
// https://vuetifyjs.com/features/sass-variables/`
|
||||
// @use 'vuetify/settings' with (
|
||||
// $color-pack: false
|
||||
// );
|
||||
// https://vuetifyjs.com/features/sass-variables/
|
||||
@use 'vuetify/settings' with (
|
||||
$body-font-family: ('line-seed', sans-serif),
|
||||
$heading-font-family: ('line-seed', sans-serif)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import Fonts from 'unplugin-fonts/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
|
||||
@@ -17,17 +16,6 @@ export default defineConfig({
|
||||
configFile: 'src/styles/settings.scss',
|
||||
},
|
||||
}),
|
||||
Fonts({
|
||||
fontsource: {
|
||||
families: [
|
||||
{
|
||||
name: 'Roboto',
|
||||
weights: [100, 300, 400, 500, 700, 900],
|
||||
styles: ['normal', 'italic'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: { 'process.env': {} },
|
||||
resolve: {
|
||||
|
||||
Generated
-156
@@ -10,9 +10,6 @@ importers:
|
||||
|
||||
app/citizen-frontend:
|
||||
dependencies:
|
||||
'@fontsource/roboto':
|
||||
specifier: ^5.2.10
|
||||
version: 5.2.10
|
||||
'@mdi/font':
|
||||
specifier: 7.4.47
|
||||
version: 7.4.47
|
||||
@@ -59,9 +56,6 @@ importers:
|
||||
typescript:
|
||||
specifier: ~5.9.3
|
||||
version: 5.9.3
|
||||
unplugin-fonts:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(vite@8.0.16(@types/node@24.13.2)(sass-embedded@1.100.0)(sass@1.100.0)(yaml@2.9.0))
|
||||
vite:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.16(@types/node@24.13.2)(sass-embedded@1.100.0)(sass@1.100.0)(yaml@2.9.0)
|
||||
@@ -206,9 +200,6 @@ packages:
|
||||
resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@fontsource/roboto@5.2.10':
|
||||
resolution: {integrity: sha512-8HlA5FtSfz//oFSr2eL7GFXAiE7eIkcGOtx7tjsLKq+as702x9+GU7K95iDeWFapHC4M2hv9RrpXKRTGGBI8Zg==}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -270,18 +261,6 @@ packages:
|
||||
'@emnapi/core': ^1.7.1
|
||||
'@emnapi/runtime': ^1.7.1
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@nodelib/fs.stat@2.0.5':
|
||||
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@nodelib/fs.walk@1.2.8':
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@ota-meshi/ast-token-store@0.3.0':
|
||||
resolution: {integrity: sha512-XRO0zi2NIUKq2lUk3T1ecFSld1fMWRKE6naRFGkgkdeosx7IslyUKNv5Dcb5PJTja9tHJoFu0v/7yEpAkrkrTg==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
@@ -720,10 +699,6 @@ packages:
|
||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
browserslist@4.28.2:
|
||||
resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
@@ -1006,10 +981,6 @@ packages:
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
|
||||
fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
|
||||
@@ -1025,9 +996,6 @@ packages:
|
||||
fast-wrap-ansi@0.2.2:
|
||||
resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
|
||||
|
||||
fastq@1.20.1:
|
||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1041,10 +1009,6 @@ packages:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
fill-range@7.1.1:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-up-simple@1.0.1:
|
||||
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1065,10 +1029,6 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
glob-parent@6.0.2:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -1123,10 +1083,6 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
is-what@5.5.0:
|
||||
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1282,14 +1238,6 @@ packages:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
minimatch@10.2.5:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -1390,10 +1338,6 @@ packages:
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@2.3.2:
|
||||
resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1444,9 +1388,6 @@ packages:
|
||||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
@@ -1471,10 +1412,6 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
reusify@1.1.0:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
@@ -1483,9 +1420,6 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
rxjs@7.8.2:
|
||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||
|
||||
@@ -1686,10 +1620,6 @@ packages:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
ts-api-utils@2.5.0:
|
||||
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -1721,23 +1651,10 @@ packages:
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
|
||||
unplugin-fonts@1.4.0:
|
||||
resolution: {integrity: sha512-TIJqr5rSlK/+3oL5nnrrEJ+Ty2taQ/bTJY1C5abYnksl553Q3HoHVqS4pnRLDkwpZq8AYqywib3kEVvHH+CtRQ==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': ^3.0.0 || ^4.0.0
|
||||
vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
|
||||
unplugin-utils@0.3.1:
|
||||
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
unplugin@2.3.5:
|
||||
resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
|
||||
unplugin@3.0.0:
|
||||
resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -2049,8 +1966,6 @@ snapshots:
|
||||
'@eslint/core': 1.2.1
|
||||
levn: 0.4.1
|
||||
|
||||
'@fontsource/roboto@5.2.10': {}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
dependencies:
|
||||
'@humanfs/types': 0.15.0
|
||||
@@ -2113,18 +2028,6 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.2
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
run-parallel: 1.2.0
|
||||
|
||||
'@nodelib/fs.stat@2.0.5': {}
|
||||
|
||||
'@nodelib/fs.walk@1.2.8':
|
||||
dependencies:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.1
|
||||
|
||||
'@ota-meshi/ast-token-store@0.3.0': {}
|
||||
|
||||
'@oxc-project/types@0.133.0': {}
|
||||
@@ -2552,10 +2455,6 @@ snapshots:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.38
|
||||
@@ -2872,14 +2771,6 @@ snapshots:
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
glob-parent: 5.1.2
|
||||
merge2: 1.4.1
|
||||
micromatch: 4.0.8
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
fast-levenshtein@2.0.6: {}
|
||||
@@ -2894,10 +2785,6 @@ snapshots:
|
||||
dependencies:
|
||||
fast-string-width: 3.0.2
|
||||
|
||||
fastq@1.20.1:
|
||||
dependencies:
|
||||
reusify: 1.1.0
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
@@ -2906,10 +2793,6 @@ snapshots:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
|
||||
fill-range@7.1.1:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
find-up-simple@1.0.1: {}
|
||||
|
||||
find-up@5.0.0:
|
||||
@@ -2927,10 +2810,6 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
glob-parent@6.0.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -2968,8 +2847,6 @@ snapshots:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-what@5.5.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
@@ -3087,13 +2964,6 @@ snapshots:
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.2
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.6
|
||||
@@ -3188,8 +3058,6 @@ snapshots:
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.2: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
pidtree@0.6.1: {}
|
||||
@@ -3236,8 +3104,6 @@ snapshots:
|
||||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
dependencies:
|
||||
json-parse-even-better-errors: 4.0.0
|
||||
@@ -3260,8 +3126,6 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rolldown@1.0.3:
|
||||
@@ -3285,10 +3149,6 @@ snapshots:
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.3
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.3
|
||||
|
||||
run-parallel@1.2.0:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
rxjs@7.8.2:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -3446,10 +3306,6 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
ts-api-utils@2.5.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -3477,23 +3333,11 @@ snapshots:
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
unplugin-fonts@1.4.0(vite@8.0.16(@types/node@24.13.2)(sass-embedded@1.100.0)(sass@1.100.0)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
fast-glob: 3.3.3
|
||||
unplugin: 2.3.5
|
||||
vite: 8.0.16(@types/node@24.13.2)(sass-embedded@1.100.0)(sass@1.100.0)(yaml@2.9.0)
|
||||
|
||||
unplugin-utils@0.3.1:
|
||||
dependencies:
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.4
|
||||
|
||||
unplugin@2.3.5:
|
||||
dependencies:
|
||||
acorn: 8.17.0
|
||||
picomatch: 4.0.4
|
||||
webpack-virtual-modules: 0.6.2
|
||||
|
||||
unplugin@3.0.0:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
|
||||
Reference in New Issue
Block a user