# Services `src/services` 是資料存取與 HTTP 邊界,負責封裝 ky client、hooks、token/session、錯誤處理與 API 模組。 ## 目前資料流 ```txt component/view -> store/composable -> service module -> httpClient -> hooks ``` 原則: - component 不直接處理底層 HTTP client、token、hooks 或錯誤正規化。 - store 或 composable 負責協調 UI 狀態與呼叫 service。 - service 回傳資料,不持有 UI 狀態。 - service 不 import component、view 或 store。 ## 目前檔案 - `client.ts`:建立單一 ky instance,設定 `prefix`、timeout、credentials 與 hooks。 - `interceptors.ts`:集中提供 ky hooks,處理 request token 注入與 response 錯誤。 - `error.ts`:提供 `normalizeError()` 與統一錯誤型別。 - `http-error.ts`:提供全域 HTTP 錯誤事件。 - `http-toast.ts`:提供 HTTP 錯誤提示相關流程。 - `token.ts`:提供 token 單一來源,並同步 localStorage。 - `session.ts`:提供 session 相關流程。 - `modules/auth.ts`:封裝登入與驗證碼 API。 - `modules/menu.ts`:封裝選單與收藏選單 API。 ## API 模組規則 新增 API 時,優先放在 `src/services/modules/.ts`。 API module 應: - 使用 `httpClient` 發 request。 - 匯出清楚命名的 API 物件,例如 `authApi`、`menuApi`。 - 定義與該 module 相關的 request/response 型別。 - 接收 `AbortSignal` 等 request option,但不管理頁面 loading 或 controller 狀態。 ## ky 使用注意事項 本專案使用 ky,不使用 axios。新增或調整 API module 時注意: - ky 不回傳 axios 的 `{ data, status, headers }` 物件。需要 JSON 時使用 `.json()`。 - 若呼叫端已經依賴 `{ data }` 形狀,請在 API module 內包回 `{ data: await ... }`,不要讓 store 或 component 混用多種 response 形狀。 - ky 的錯誤型別是 `HTTPError`、`TimeoutError` 等,不是 `AxiosError`。錯誤一律交給 `normalizeError()`,呼叫端不要直接判斷 ky error。 - ky 基於 Fetch API,取消請求使用原生 `AbortController` 與 `signal`。 - token 注入、401 force logout、HTTP 錯誤導頁與 toast 都集中在 ky hooks。不要在單一 service module 裡重複實作。 - FormData 請用 `body: formData`;JSON payload 請用 `json: payload`。 - 如果需求需要 upload progress、request/response transform、或其他 axios 專屬行為,先確認 ky/fetch 是否有等價做法,再決定是否擴充 service layer。 ## HTTP Client 設定 `client.ts` 的 `baseURL` 優先使用 `VITE_API_BASE_URL`,否則使用 `/service/api`。開發模式下,Vite proxy 會將 `/service/*` 轉送到後端。 template 提供 `.env.example` 作為環境變數範本。建立新專案時,複製成 `.env` 或對應 mode 的 env 檔,再填入實際 API 設定。 ```bash cp .env.example .env ``` 目前 `vite.config.mts` 的 dev proxy 會將 `/service/` 轉送到範例後端。正式專案若後端不同,優先調整 env 或 Vite proxy 設定,不要逐一修改 service module 裡的 endpoint。 production 不應沿用 template 內的示範後端位址,應由使用專案自己的部署環境提供 `VITE_API_BASE_URL`。 目前 API 呼叫範例: - `authApi.getCaptcha()` -> `/Auth/get-captcha` - `authApi.login()` -> `/Auth/login` - `menuApi.getMenu()` -> `/Menu/GetMenu` - `menuApi.getFavorite()` -> `/Menu/GetFavorite` ## Token 與錯誤處理 token 由 `tokenService` 作為單一來源: - store 負責登入成功後寫入 token,以及登出時清除 token。 - hooks 只讀取 token 並附加到 request。 - 401 或 HTTP 錯誤由 hooks 與錯誤事件流程集中處理。 錯誤透過 `normalizeError()` 轉成 UI 可理解的格式。UI 或 store 不需要直接理解 ky 的 HTTPError。 ## 請求取消 需要取消請求時,由 store 或 composable 建立 `AbortController`,service module 只接收 `signal`。不要讓 service module 持有 controller 或 UI 狀態。