feat:實作 Member Portal MVP 前端與後端整合

後端:
- 新增 DivingOffer Model / DivingOfferController(列表+詳情 API,支援搜尋/篩選/分頁)
- 修正 Google OAuth callback 改為 redirect 至前端(SocialAuthController)
- 新增 config/cors.php 允許前端 origin
- .gitignore 新增 frontend/ 排除規則

前端(frontend/):
- Vue 3 + Vite + Tailwind CSS + Pinia + Vue Router
- 頁面:首頁、課程列表、課程詳情、登入、註冊、個人資料、OAuth callback
- 整合至 Docker(multi-stage build,nginx 靜態服務於 port 5173)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 01:41:28 +08:00
parent 725c86f434
commit 550e2fc97a
48 changed files with 5887 additions and 17 deletions
@@ -0,0 +1,73 @@
## 1. [後端] 環境與 CORS 設定
- [x] 1.1 在 `.env` 新增 `FRONTEND_URL=http://localhost:5173``GOOGLE_CLIENT_ID``GOOGLE_CLIENT_SECRET``GOOGLE_REDIRECT_URI=http://localhost:80/api/auth/google/callback`
- [x] 1.2 執行 `php artisan config:publish cors` 建立 `config/cors.php`,設定 `allowed_origins=[FRONTEND_URL]``allowed_methods``allowed_headers``supports_credentials=false`(參考 design.md Contract 3
- [x] 1.3 確認 `bootstrap/app.php`(或 `app/Http/Kernel.php`)已啟用 `HandleCors` middleware
## 2. [後端] 修正 Google OAuth Callback
- [x] 2.1 修改 `SocialAuthController::handleGoogleCallback()`:成功時改為 `redirect(env('FRONTEND_URL') . '/auth/callback?token=' . $token)`
- [x] 2.2 修改 catch 區塊:失敗時改為 `redirect(env('FRONTEND_URL') . '/login?error=oauth_failed')`
- [x] 2.3 手動測試 OAuth 流程:點擊 Google 登入後確認瀏覽器最終落在 `:5173/auth/callback?token=...`
## 3. [後端] Diving Offers API
- [x] 3.1 更新 `DivingOffer` Model:設定 `$fillable``$table``badges` 欄位加上 `$casts = ['badges' => 'array']` 自動 JSON decode
- [x] 3.2 建立 `DivingOfferController`,實作 `index()` 方法(支援 q / region / tag 篩選,分頁預設 12 筆,max 50)
- [x] 3.3 實作 `show($id)` 方法:找不到時回傳 `{ "status": false, "message": "課程不存在" }`HTTP 404
- [x] 3.4 在 `routes/api.php` 新增公開路由:`GET /diving-offers``GET /diving-offers/{id}`
- [x] 3.5 用 Postman 驗證:列表(含 q / region / tag / 分頁)、詳情、404 情境,確認 response 結構符合 design.md Contract 1
## 4. [前端] 專案初始化
- [x] 4.1 在 Laravel repo 外建立新目錄 `cf-dive-frontend`,執行 `npm create vite@latest . -- --template vue`
- [x] 4.2 安裝依賴:`npm install`,再安裝 `vue-router@4 pinia axios`
- [x] 4.3 安裝並設定 Tailwind CSS`tailwindcss postcss autoprefixer`,初始化 `tailwind.config.js`
- [x] 4.4 建立 `.env` 文件,設定 `VITE_API_URL=http://localhost:80`
- [x] 4.5 建立 `src/api/axios.js`:設定 Axios instancebase URL 讀自 `import.meta.env.VITE_API_URL`request interceptor 讀 localStorage token 並附加 `Authorization: Bearer <token>`
- [x] 4.6 建立 `src/stores/auth.js`Pinia store 管理 `user``token``isLoggedIn``init()` 從 localStorage 還原狀態
- [x] 4.7 設定 Vue Router`src/router/index.js`):定義所有路由(含 `/auth/callback`),`/profile` 加上 beforeEach navigation guard(未登入導向 `/login`
- [x] 4.8 在 `App.vue` 呼叫 `authStore.init()`,並加入 `<RouterView>`
- [x] 4.9 執行 `npm run dev`,確認開發環境正常啟動無錯誤
## 5. [前端] Layout 與共用組件
- [x] 5.1 建立 `src/components/NavBar.vue`:顯示 logo、「探索課程」連結,已登入顯示「個人資料」和「登出」,未登入顯示「登入」和「註冊」
- [x] 5.2 建立 `src/components/CourseCard.vue`:接收 offer 資料,顯示標題、地點、價格、評分、標籤
## 6. [前端] 首頁
- [x] 6.1 建立 `src/views/HomeView.vue`Hero section(平台名稱、簡介)+ 「探索課程」CTA 按鈕,點擊導向 `/courses`
## 7. [前端] 課程列表頁
- [x] 7.1 建立 `src/views/CoursesView.vue`,掛載時呼叫 `GET /api/diving-offers`,渲染 `CourseCard` 列表
- [x] 7.2 新增搜尋框:輸入後按 Enter 或點搜尋重新呼叫 API(帶 `q` 參數)
- [x] 7.3 新增地區下拉選單:選擇後以 `region` 參數重新呼叫 API
- [x] 7.4 處理無結果狀態:顯示「找不到符合的課程」提示
## 8. [前端] 課程詳情頁
- [x] 8.1 建立 `src/views/CourseDetailView.vue`,掛載時呼叫 `GET /api/diving-offers/:id`
- [x] 8.2 顯示課程完整資訊:標題、地點、景點、價格、評分、評論數、描述、徽章(badges 陣列)、標籤
- [x] 8.3 處理 404 情境:顯示「課程不存在」並提供「返回列表」按鈕
## 9. [前端] 認證頁面
- [x] 9.1 建立 `src/views/LoginView.vue`email/password 表單,送出呼叫 `POST /api/member/login`,成功存 token + user 至 Pinia 並導向 `/courses`,失敗顯示錯誤訊息
- [x] 9.2 在 `LoginView.vue` 加入「以 Google 登入」按鈕:點擊執行 `window.location.href = VITE_API_URL + '/api/auth/google/redirect'`
- [x] 9.3 建立 `src/views/AuthCallbackView.vue`(路由 `/auth/callback`):讀取 `?token=` query param → 存入 Pinia + localStorage → 呼叫 `history.replaceState` 清除 URL token → 導向 `/courses`;若 `?error=oauth_failed` 則導向 `/login` 並顯示錯誤提示
- [x] 9.4 建立 `src/views/RegisterView.vue`name / email / password / password_confirmation 表單,送出呼叫 `POST /api/member/register`,成功導向 `/login` 並顯示成功提示,失敗顯示錯誤
## 10. [前端] 會員個人資料頁
- [x] 10.1 建立 `src/views/ProfileView.vue`,掛載時呼叫 `GET /api/member/profile`,顯示姓名、email、生日、性別、地址、緊急聯絡人
- [x] 10.2 實作編輯表單:使用者修改後點擊「儲存」呼叫 `PUT /api/member/profile`,成功顯示「資料已更新」提示
## 11. [整合測試] 端對端驗證
- [x] 11.1 驗證訪客流程:首頁 → 課程列表(搜尋/篩選)→ 課程詳情(無需登入)
- [x] 11.2 驗證 Email 認證流程:註冊 → 登入 → 個人資料 → 登出
- [x] 11.3 驗證 Google OAuth 流程:點擊 Google 登入 → 同意 → 回到前端 `/auth/callback` → 自動存 token → 導向課程列表
- [x] 11.4 驗證 navigation guard:未登入直接訪問 `/profile` 自動跳轉至 `/login`
- [x] 11.5 驗證 CORS:確認 Network tab 無 CORS 錯誤,所有 API 請求正常回應