--- jd-id: "01.05" name: "src-stores" desc: "Pinia stores. The auth store owns the JWT lifecycle, axios interceptor setup, and route push; a counter stub remains for the default scaffolding." author: "Clark Lin" updateTime: "2026-07-06" status: active type: "A-高层系统" tags: - 前端 - Pinia - StateManagement - JWT --- ## Core Files | File path | Role | |-----------|------| | `src/stores/auth.js` | The single production store. Defines `useAuthStore` (setup-style), persists tokens in `localStorage`, decodes JWT claims, exposes `isAuthenticated` / `isAccessTokenExpired` / `isAccessTokenExpiringSoon`, and wires the axios request/response interceptors exactly once (deduped via a module-level `refreshPromise`). | | `src/stores/counter.js` | Boilerplate from the Vue scaffold; unused by the app. | --- ## Auth Store Surface - **State** — `accessToken`, `refreshToken`, `user`, `interceptorsInitialized`, `isRefreshing` (`refreshPromise` is a module-level `let`). - **Computed** — `isAuthenticated` (`!!accessToken`). - **JWT helpers** — `decodeJwt(jwt)`, `getAccessTokenExp()`, `isAccessTokenExpired()`, `isAccessTokenExpiringSoon(thresholdSeconds = 60)`. - **Axios wiring** — `setAxiosAuthHeader(token)`, `initializeAuth()` (called once from `main.js`), and `refreshAccessToken()` (uses the module-level `refreshPromise` to dedupe concurrent refreshes; second 401 in a request → `logout()`). - **Lifecycle** — `login(credentials)` navigates on success; `logout()` clears tokens and routes back to `/login`. > Login-endpoint 401s are intentionally **not** passed through the retry flow — only requests that hit a regular endpoint (not `/api/token/`) get the refresh + retry treatment. --- Home: [[01.00 student-app-frontend]] | **Location:** `C:\Projects\vue\student-app-frontend\src\stores`