Files
CFDivePlatform/openspec/changes/archive/2026-05-10-admin-panel/design.md
T
a620906209 ad2c05779d feat:實作 Admin Panel — 平台管理後台
後端:
- AdminStatsController:總會員/教練/課程數統計 API
- AdminUserController:會員與教練列表、詳情、啟用/停用、教練驗證(toggle 反轉語意)
- AdminOfferController:全平台課程列表與刪除
- routes/api.php:新增 /api/admin/stats、members、providers、offers 等路由

前端(frontend/):
- adminAuth store、adminAxios(第三套獨立認證)
- /admin/* 路由群組 + requiresAdmin guard
- AdminNavBar、AdminLayout
- App.vue:isCoachPage → isBackofficePage(/coach/* 和 /admin/* 皆隱藏會員 NavBar)
- LoginView、DashboardView(統計卡片)
- MembersView、ProvidersView(含驗證操作)、OffersView(含刪除確認)

OpenSpec:
- 新增 specs:admin-auth / admin-user-management / admin-offer-management / admin-stats / admin-panel-ui
- 歸檔:openspec/changes/archive/2026-05-10-admin-panel

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 04:07:13 +08:00

4.4 KiB
Raw Blame History

Context

後端 Admin Auth 方法(loginAdmin / logoutAdmin / adminProfile / updateAdminProfile)已存在於 AuthController,路由也已佔位。User model 有 is_active 欄位;ProviderProfile 有 is_verified 欄位,目前無 API 可修改。前端已有 memberAuth / coachAuth 兩套認證模式,Admin 循同一模式新增第三套。

Goals / Non-Goals

Goals:

  • 管理員可登入後台、查看平台數據
  • 管理員可列表/搜尋會員與教練,並啟用/停用帳號
  • 管理員可驗證教練(設定 ProviderProfile.is_verified
  • 管理員可查看全平台課程並刪除違規內容
  • 前端 /admin/* 有獨立 Layout,不顯示會員 NavBar

Non-Goals:

  • Admin 帳號自助註冊(透過後端 seeder 或直接 DB 建立)
  • 細粒度角色權限(RBAC
  • 操作日誌(Audit Log
  • 批次操作(批量停用)

Decisions

D1Admin Auth 沿用現有 AuthController,不新建 Controller

決定loginAdminlogoutAdminadminProfileupdateAdminProfile 直接沿用,不修改。

理由:方法已存在且邏輯完整,路由也已佔位。


D2:業務邏輯拆到獨立 Controller

決定:新增 AdminUserController(用戶管理)、AdminOfferController(課程管理)、AdminStatsController(統計)。

理由:與 AuthController 職責分開,避免繼續膨脹。所有方法在開頭驗證 auth()->user()->role === 'admin',非管理員回傳 403。


D3:Toggle 語意(啟用/停用、驗證/取消驗證)

決定toggle-activetoggle-verified 為 PUT 端點,後端直接反轉當前值(is_active = !is_active),不接受 body 傳入布林值。

理由:UI 是單一按鈕切換狀態,反轉語意最直覺,避免前端傳錯值。


D4adminAuth 獨立 StorelocalStorage key admin_token / admin_user

決定:循 coachAuth 模式,新增第三套獨立 store。

理由:三種角色可能在不同 tab 同時使用,共用 store 會互相污染。


Contracts

API Schema

POST /api/admin/login(現有)

Body: { "email": "...", "password": "..." }
Response 200: { "status": true, "data": { "user": {...}, "token": "...", "token_type": "Bearer" } }

GET /api/admin/stats(需 Bearer tokenrole=admin

Response 200:
{
  "status": true,
  "data": {
    "total_members":   120,
    "total_providers": 18,
    "total_offers":    64
  }
}

GET /api/admin/members(需 Bearer tokenrole=admin

Query: q(搜尋 name / email, page, per_pagedefault 15
Response 200: { "status": true, "data": [...users with memberProfile], "meta": {...} }

GET /api/admin/members/{id}

Response 200: { "status": true, "data": { ...user, profile: {...} } }
Response 404: { "status": false, "message": "用戶不存在" }

PUT /api/admin/members/{id}/toggle-active

Response 200: { "status": true, "message": "帳號已停用" | "帳號已啟用", "data": { "is_active": false | true } }
Response 404: { "status": false, "message": "用戶不存在" }

GET /api/admin/providers(同 members 結構,含 providerProfile

GET /api/admin/providers/{id}

PUT /api/admin/providers/{id}/toggle-active

PUT /api/admin/providers/{id}/toggle-verified

Response 200: { "status": true, "message": "教練已驗證" | "已取消驗證", "data": { "is_verified": true | false } }

GET /api/admin/offers

Query: q(搜尋 title / location, page, per_pagedefault 15
Response 200: { "status": true, "data": [...offers with provider_id], "meta": {...} }

DELETE /api/admin/offers/{id}

Response 200: { "status": true, "message": "課程已刪除" }
Response 404: { "status": false, "message": "課程不存在" }

Risks / Trade-offs

風險 緩解策略
toggle 反轉語意若網路重試,可能連按兩次回到原狀態 MVP 接受,未來可改為明確 { is_active: true/false } body
Admin 帳號只能透過 DB 或 seeder 建立,無自助註冊 開發期間用 tinker 建立,正式環境透過 seeder
AdminUserController 對 member / provider 各需重複驗證邏輯 用 private helper method 共用,避免複製貼上
/admin/* 頁面無額外安全層(任何人知道路徑都可訪問登入頁) MVP 接受,route guard 在 frontend 層足夠