Files
skt-vuetify-templates/src/services/README.md
T

115 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
元件 (Component)
↓ 呼叫
Store (Pinia) ← 管理狀態、快取
↓ 呼叫
API Service ← 封裝業務邏輯
↓ 呼叫
HTTP Client ← Axios 實例、攔截器
## 目前的資料流(以登入為例)
1. `views/Login.vue`Playground 頁面)只負責表單/驗證碼/導頁等 UI 行為
2. `stores/auth.ts` 統一負責登入狀態(`user`/`token`/`loading`/`error`
3. `services/modules/user.ts` 封裝 `login/getProfile/...` 端點
4. `services/client.ts` 建立 `axios` instance
5. `services/interceptors.ts` 統一注入 token 與處理 HTTP 錯誤
## Menu API 與資料結構
選單系統採用 API 驅動設計:
### API 端點
- `GET /service/api/menu`:取得完整選單樹
- `GET /service/api/menu/favorite`:取得使用者收藏選單
### 資料結構
```ts
interface MenuNode {
mdl_id: string // 模組 ID
mdl_name: string // 模組名稱
unt_id?: string // 單位 ID
unt_name?: string // 單位名稱
fnc_id?: string // 功能 ID
fnc_name?: string // 功能名稱
children?: MenuNode[]
}
```
### 階層關係
- **第一層**:模組(mdl
- **第二層**:單位(unt
- **第三層**:功能(fnc),作為葉節點使用 `fnc_id` 作為路由路徑
### Store 持久化
`stores/menu.ts` 提供:
- 自動 localStorage 持久化選單與收藏
- 初始化時自動還原資料
- 登出時清除快取
## API 前綴:`/api`
目前 Playground 已將 `api` 資料夾更名為 `services`,避免與 API 前綴 `/api` 衝突。
在開發模式下:
- 前端呼叫一律使用 `/service/api/*`
- Vite dev server 透過 proxy 將 `/service/*` 轉送到後端(目前指向 `http://192.168.89.54:9002`
## HTTP Client 設定
- `baseURL`:優先使用 `VITE_API_BASE_URL`,否則預設 `/service/api`(搭配 Vite proxy
- `Content-Type`:預設 `application/json`
## Token Service(單一來源)
為避免「Pinia token」與「localStorage token」不同步的問題,這裡採用單一來源:
- `services/token.ts` 使用 `ref` 保存 token,並同步 localStorage
- Store 與 Interceptor 都只透過 `tokenService` 讀寫 token
- 401 時清除 token,可立即同步到 UI
## Token 注入策略(Interceptor
Interceptor 會從 `tokenService` 讀取 `token` 並注入 `Authorization: Bearer <token>`
這樣做的原因是避免循環依賴:
`store(auth) -> services(userApi) -> httpClient -> interceptors -> store(auth)`
Store 仍然是「唯一負責更新 token 的地方」,Interceptor 只負責「讀取 token 並附加到 request」。
## 錯誤正規化(normalizeError
為了讓 UI 不需要理解 AxiosError,這裡將錯誤統一成 `ApiRequestError`
- `services/error.ts` 提供 `normalizeError()``ApiRequestError`
- Interceptor 在 response error 時呼叫 `normalizeError()`
- Store 只需要處理 `error.message / error.code / error.status`
最低限度的映射規則:
-`response.data.message` 優先使用
- 其次使用 `AxiosError.message`
- 都沒有則顯示 `請求失敗`
## 請求取消(AbortController
取消策略採「同類型請求互斥」,目前示範在 `login`
- Store 建立 `AbortController`,每次登入前先取消前一次
- Service 只接收 `signal`,不管理 controller 狀態
- `normalizeError` 會將取消行為轉為 `CanceledRequestError`
- UI 不顯示取消造成的錯誤訊息
| DECISION | WHY | WHY NOT |
|---|---|---|
| Store 呼叫 API,不是元件直接呼叫 | 狀態集中管理、可快取、易測試 | 元件直接呼叫會導致狀態散落 |
| API 模組化(userApi、orderApi)| 關注點分離、好維護 | 全塞一個檔案會變超大 |
| Interceptor 獨立檔案| 單一職責、好測試 | 寫在 client.ts 會雜亂 |
| 泛型 ApiResponse<T>| 型別安全、IDE 提示 | 用 any 會失去 TS 優勢 |
| API 前綴使用 `/api` | 使用習慣一致、搭配 Vite proxy 容易理解 | 若前端資料夾也叫 `api` 容易造成路徑衝突,因此此專案改名為 `services` |