diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index d6a7a90..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -MUST READ @AGENTS.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/app/citizen-frontend/.env.example b/app/citizen-frontend/.env.example new file mode 100644 index 0000000..3f0bad7 --- /dev/null +++ b/app/citizen-frontend/.env.example @@ -0,0 +1,5 @@ +# 後端 API base URL;留空則使用同網域的 /api +VITE_API_BASE_URL= + +# 後端尚未完成前保持 true,改為 false 才會實際發出 API 請求 +VITE_USE_MOCK=true diff --git a/app/citizen-frontend/env.d.ts b/app/citizen-frontend/env.d.ts index db56195..c3be18c 100644 --- a/app/citizen-frontend/env.d.ts +++ b/app/citizen-frontend/env.d.ts @@ -1,2 +1,13 @@ /// /// + +interface ImportMetaEnv { + /** 後端 API 的 base URL,未設定時走 Vite dev server 的 /api */ + readonly VITE_API_BASE_URL?: string + /** 設為 'false' 才會真正打後端,其餘情況一律使用前端模擬資料 */ + readonly VITE_USE_MOCK?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/app/citizen-frontend/package.json b/app/citizen-frontend/package.json index a441a9e..b3f3eb1 100644 --- a/app/citizen-frontend/package.json +++ b/app/citizen-frontend/package.json @@ -14,24 +14,25 @@ }, "dependencies": { "@mdi/font": "7.4.47", - "vue": "^3.5.30", - "vuetify": "^4.0.2", + "ky": "^2.0.2", "pinia": "^3.0.4", + "vue": "^3.5.30", "vue-i18n": "^11.3.0", - "vue-router": "^5.0.3" + "vue-router": "^5.0.3", + "vuetify": "^4.0.2" }, "devDependencies": { "@tsconfig/node22": "^22.0.5", "@types/node": "^24.12.0", "@vitejs/plugin-vue": "^6.0.5", "@vue/tsconfig": "^0.9.0", + "eslint": "^9.39.4", + "eslint-config-vuetify": "^4.3.4", "npm-run-all2": "^8.0.4", "sass-embedded": "^1.98.0", "typescript": "~5.9.3", "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" + "vue-tsc": "^3.2.5" } } diff --git a/app/citizen-frontend/src/api/client.ts b/app/citizen-frontend/src/api/client.ts new file mode 100644 index 0000000..3e91487 --- /dev/null +++ b/app/citizen-frontend/src/api/client.ts @@ -0,0 +1,12 @@ +import ky from 'ky' + +/** + * 全站共用的 HTTP client。 + * base URL、逾時與重試策略集中在此設定,各 API 模組只描述端點與型別。 + */ +export const api = ky.create({ + // prefix 同時接受相對路徑(/api)與絕對網址,兩種部署情境都涵蓋 + prefix: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 10_000, + retry: { limit: 2 }, +}) diff --git a/app/citizen-frontend/src/api/course.ts b/app/citizen-frontend/src/api/course.ts new file mode 100644 index 0000000..1159ee5 --- /dev/null +++ b/app/citizen-frontend/src/api/course.ts @@ -0,0 +1,32 @@ +import type { CourseList, CourseListParams, FitnessGroup, YesNo } from './types' +import { api } from './client' + +/** + * 課程建議的 5 支 API。 + * 端點路徑集中在此檔,後端定案後只需改這裡。 + */ + +/** 取得使用者的體適能組別 */ +export function fetchGroup (): Promise { + return api.get('course/group').json() +} + +/** 取得是否納入早上時段 */ +export function fetchMorning (): Promise { + return api.get('course/morning').json() +} + +/** 取得是否納入中午時段 */ +export function fetchNoon (): Promise { + return api.get('course/noon').json() +} + +/** 取得是否納入晚上時段 */ +export function fetchNight (): Promise { + return api.get('course/night').json() +} + +/** 以組別與時段條件取得一週課表 */ +export function fetchCourseList (params: CourseListParams): Promise { + return api.get('course/list', { searchParams: { ...params } }).json() +} diff --git a/app/citizen-frontend/src/api/types.ts b/app/citizen-frontend/src/api/types.ts new file mode 100644 index 0000000..b9dc8e8 --- /dev/null +++ b/app/citizen-frontend/src/api/types.ts @@ -0,0 +1,32 @@ +/** + * 課程建議相關的 API 契約。 + * 後端尚未開發,這份型別即為前後端約定的介面。 + */ + +/** 體適能組別 */ +export type FitnessGroup = 'low' | 'medium' | 'high' + +/** 後端以 Y/N 字串表示布林值 */ +export type YesNo = 'Y' | 'N' + +/** 課表的三個時段 */ +export type TimeSlot = 'morning' | 'noon' | 'night' + +/** 課表的一天;未被選取的時段其欄位為 undefined */ +export interface CourseRow { + /** 1 = 週一 … 7 = 週日 */ + day: number + morning?: string + noon?: string + night?: string +} + +export type CourseList = CourseRow[] + +/** 取得課表所需的查詢參數 */ +export interface CourseListParams { + group: FitnessGroup + morning: YesNo + noon: YesNo + night: YesNo +} diff --git a/app/citizen-frontend/src/components/layout/DefaultLayout.vue b/app/citizen-frontend/src/components/layout/DefaultLayout.vue index 69414e3..74b4c38 100644 --- a/app/citizen-frontend/src/components/layout/DefaultLayout.vue +++ b/app/citizen-frontend/src/components/layout/DefaultLayout.vue @@ -102,6 +102,14 @@ to="/self-assessment" /> + + + + + + + + + 課程建議 + + + + + {{ store.groupMeta.label }} + + + + + {{ store.groupMeta.description }} + + + + 選擇要顯示的時段 + + + + {{ slot.label }} + + + + + + + + + + + + + {{ store.error }} + + + 重試 + + + + + + 目前沒有可顯示的課程,請至少選擇一個時段。 + + + + + + + + + 星期 + + + + {{ slot.label }} + + + + + + + + {{ DAY_NAMES[row.day - 1] }} + + + + {{ row[slot.key] }} + + + + + + + + + + + + + diff --git a/app/citizen-frontend/src/pages/index.vue b/app/citizen-frontend/src/pages/index.vue index 5472ec5..b963be9 100644 --- a/app/citizen-frontend/src/pages/index.vue +++ b/app/citizen-frontend/src/pages/index.vue @@ -54,18 +54,18 @@ - + - - + + - 運動處方建議: - + 課程建議 + - - {{ currentWeeklyPrescription }} - - + + 依你的體適能組別,查看一週的每日課表 + + @@ -110,82 +110,6 @@ title="預約" to="/appointment" /> - - - - - - - 本週運動處方明細 - - - - 每週訓練任務進度 - - - - 任務完成度 (2/7堂課) - 28% - - - - - - - - - - - - - 下肢肌力:坐站起立訓練 - - - 每週 2 次,每次 3 組,每組 10-12 次。 - * 扶手保護,站起時吐氣。 - - - - - - - - - 靜態平衡:扶椅單腳站立 - - - 每週 3 次,左右腳各 30 秒,重複 3 次。 - 手輕扶椅背,確保安全。 - - - - - - - - - 心肺耐力:每日快走 - - - 每日 20-30 分鐘,達到微喘但仍可說話的強度。 - * 若感覺頭暈或膝蓋疼痛請暫停! - - - - - - - - 確認關閉 - - - - @@ -268,7 +192,6 @@ 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 @@ -325,17 +248,6 @@ const todayTimeString = computed(() => { return `${date} ${time}` }) - -// 每週處方文字 -const currentWeeklyPrescription = computed(() => { - if (store.healthScore >= 80) { - return '1次肌力循環 + 1次平衡課 + 每日20分鐘快走' - } else if (store.healthScore >= 65) { - return '2次肌力循環 + 1次平衡課 + 每日20分鐘快走' - } else { - return '3次防跌平衡引導 + 每日15分鐘扶椅坐站與慢走' - } -})
- {{ currentWeeklyPrescription }} -