Files
CFDivePlatform/openspec/changes/archive/2026-05-17-notification-system/tasks.md
T
a620906209 03f8caf3e9 feat:實作通知系統 — 站內通知、Email 通知、Polling 機制
後端
- 新增 6 個 Notification class(預約建立/確認/拒絕/取消/完成、收到評價),database + mail 雙 channel
- 新增 NotificationController(list / unread-count / markRead / markAllRead / destroy)
- 整合通知觸發至 MemberBookingController、ProviderBookingController、CompleteFinishedBookings、ReviewController
- 新增 notifications / jobs / failed_jobs migration
- Docker Compose 加入 queue-worker、mailpit service
- DivingOffer 補上 provider() 關聯

前端
- 新增 notificationStore(Polling 30s/60s 自適應 + Page Visibility API)
- 新增 NotificationBell(未讀 Badge)、NotificationDrawer(側邊通知中心)
- main.js:auth store init 前置於 router.use(),修正 beforeEach guard 時序問題
- notificationAxios:依路徑動態選擇 member/coach token
- NotificationDrawer:改用 new URL().pathname 提取 action_url 路徑

OpenSpec
- 歸檔 notification-system change
- 同步 notification-core / notification-email / notification-triggers specs 至主規格
- 更新 booking-lifecycle / review-lifecycle spec(補充通知觸發 requirement)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-17 22:26:14 +08:00

7.8 KiB
Raw Blame History

0. 前置設定

  • 0.1 [後端] config/app.php 加入 'frontend_url' => env('FRONTEND_URL', 'http://localhost:5173')
  • 0.2 [後端] .env.example 補上 FRONTEND_URL=http://localhost:5173

1. 基礎設施:資料庫與 Queue

  • 1.1 [後端] 執行 php artisan notifications:table 產生 notifications migration,確認 schema 欄位正確
  • 1.2 [後端] 執行 php artisan queue:table 產生 jobs / failed_jobs migration(若尚未存在)
  • 1.3 [後端] 執行 php artisan migrate 建立兩張資料表(需 Docker 啟動後執行)
  • 1.4 [後端] 在 .env 設定 QUEUE_CONNECTION=database(已存在)
  • 1.5 [後端] docker-compose.yml 新增 queue-worker servicephp artisan queue:work --sleep=3 --tries=3
  • 1.6 [後端] docker-compose.yml 新增 mailpit serviceimage: axllent/mailpitport 1025/8025),.env 設定 MAIL_HOST=mailpit MAIL_PORT=1025

2. Notification Classes(後端)

  • 2.1 [後端] 建立 app/Notifications/BookingCreatedNotification.phpvia() 回傳 ['database', 'mail'],實作 toArray()toMail()
  • 2.2 [後端] 建立 app/Notifications/BookingConfirmedNotification.phpvia: database + mail
  • 2.3 [後端] 建立 app/Notifications/BookingRejectedNotification.phpvia: database + mail
  • 2.4 [後端] 建立 app/Notifications/BookingCancelledNotification.phpvia: database + mail,含 cancelledBy 參數)
  • 2.5 [後端] 建立 app/Notifications/BookingCompletedNotification.phpvia: database + mail
  • 2.6 [後端] 建立 app/Notifications/ReviewReceivedNotification.phpvia: database 僅站內)
  • 2.7 [後端] 所有 Notification class 的 toArray() 回傳統一結構:{ type, title, body, action_url, related_id, related_type }

3. Email Markdown 模板

  • 3.1 [後端] 建立 resources/views/emails/notifications/booking-created.blade.phpMarkdown)(改用 toMail() 內聯實作)
  • 3.2 [後端] 建立 resources/views/emails/notifications/booking-confirmed.blade.php
  • 3.3 [後端] 建立 resources/views/emails/notifications/booking-rejected.blade.php
  • 3.4 [後端] 建立 resources/views/emails/notifications/booking-cancelled.blade.php
  • 3.5 [後端] 建立 resources/views/emails/notifications/booking-completed.blade.php
  • 3.6 [後端] 確認所有模板包含:平台名稱、通知標題、正文、CTA 按鈕(action_url)、底部免責聲明

4. Notification API(後端)

  • 4.1 [後端] 建立 app/Http/Controllers/Api/NotificationController.php,實作 index()unreadCount()markRead()markAllRead()destroy()
  • 4.2 [後端] routes/api.php 新增路由群組(Sanctum middleware
  • 4.3 [後端] index() 分頁 20 筆,依 created_at DESCresponse 含 unread_countmeta
  • 4.4 [後端] markRead() / destroy() 驗證通知屬於當前使用者(findOrFail 在 user->notifications() 作用域內自動限制)

5. 業務觸發整合(後端,無 Service 層,直接插入 Controller

  • 5.1 [後端] app/Models/DivingOffer.php 補上 provider() 關聯
  • 5.2 [後端] 確認 app/Models/User.php 已使用 Notifiable trait(已存在)
  • 5.3 [後端] MemberBookingController::store()notify BookingCreatedNotification
  • 5.4 [後端] ProviderBookingController::confirm()notify BookingConfirmedNotification
  • 5.5 [後端] ProviderBookingController::reject()notify BookingRejectedNotification
  • 5.6 [後端] MemberBookingController::cancel()notify BookingCancelledNotification(cancelledBy: 'member')
  • 5.7 [後端] ProviderBookingController::cancel()notify BookingCancelledNotification(cancelledBy: 'provider')
  • 5.8 [後端] ProviderBookingController::complete()notify BookingCompletedNotification
  • 5.9 [後端] CompleteFinishedBookings::handle():改為 get()+loop,逐筆 notify
  • 5.10 [後端] ReviewController::store()notify ReviewReceivedNotification

6. 前端 Pinia Store

  • 6.1 [前端] 建立 frontend/src/stores/notifications.js,含 state: { unreadCount, notifications, isOpen }
  • 6.2 [前端] notificationStore.startPolling():登入後立即 fetch 一次,未讀 > 0 每 30 秒、= 0 每 60 秒;count 改變時 clearInterval 重啟新間隔
  • 6.3 [前端] Page Visibility API 整合:visibilitychange = hidden 暫停 interval= visible 立即 fetch 並重啟
  • 6.4 [前端] notificationStore.stopPolling():登出時 clearInterval + removeEventListener('visibilitychange')
  • 6.5 [前端] notificationStore.fetchNotifications():呼叫 GET /api/notifications,更新 notificationsunreadCount
  • 6.6 [前端] notificationStore.markRead(id) / markAllRead() / remove(id) actionsmarkRead 採 Optimistic update

7. 前端通知元件

  • 7.1 [前端] 建立 frontend/src/components/NotificationBell.vueBell Icon + 未讀 Badge(紅色,count > 0 才顯示)
  • 7.2 [前端] 建立 frontend/src/components/NotificationDrawer.vue:側邊 Drawer,列出通知列表,每項顯示 title / body(截 80 字)/ 相對時間 / 已讀狀態
  • 7.3 [前端] Drawer 頂部加「全部標為已讀」按鈕,點擊後呼叫 markAllRead()
  • 7.4 [前端] 點擊通知項目:呼叫 markRead(id)router.push(action_url)
  • 7.5 [前端] 點擊通知項目右側刪除 Icon:呼叫 remove(id)

8. 整合至 NavBar

  • 8.1 [前端] frontend/src/components/NavBar.vueMember):加入 <NotificationBell />
  • 8.2 [前端] frontend/src/components/CoachNavBar.vueCoach):加入 Bell Icon
  • 8.3 [前端] frontend/src/App.vue:加入 <NotificationDrawer />
  • 8.4 [前端] frontend/src/stores/auth.jssetAuth/init 呼叫 startPollinglogout 呼叫 stopPolling
  • 8.5 [前端] frontend/src/stores/coachAuth.js:同上整合 polling 生命週期

9. 手動驗證

  • 9.1 [整合測試] 啟動 Docker Compose(含 queue-worker + mailpit),確認所有 service 正常
  • 9.2 [整合測試] Member 建立預約 → Provider 站內通知出現 + Mailpit 收到信
  • 9.3 [整合測試] Provider 確認預約 → Member 站內通知出現 + Email
  • 9.4 [整合測試] Member 提交評價 → Provider 站內通知出現(無 Email
  • 9.5 [整合測試] Bell Icon 未讀 Badge 顯示正確數量,全部標已讀後 Badge 消失
  • 9.6 [整合測試] 點擊通知項目 → 標記已讀 → 跳轉 action_url
  • 9.7 [整合測試] Mailpit Web UIhttp://localhost:8025)確認 Email 格式與 CTA 連結正確

10. 整合測試中發現的 Bug 修正

  • 10.1 [前端] main.js:將三個 auth store 的 init() 移至 app.use(router) 之前執行,修正 beforeEach guard 在 store 初始化前跑導致 protected route 被誤踢的問題
  • 10.2 [後端] BookingConfirmedNotification / BookingRejectedNotification / BookingCancelledNotificationaction_url 移除 /{booking.id} 尾綴,改為 {FRONTEND_URL}/my-bookings(前端路由無 /my-bookings/:id 詳情頁)
  • 10.3 [資料庫] 修正已存在的歷史通知中錯誤的 action_urlUPDATE notifications SET data = JSON_SET(...)
  • 10.4 [後端] 建立 failed_jobs 資料表(php artisan queue:failed-table && php artisan migrate),修正 queue job 失敗時無法寫入錯誤記錄的問題
  • 10.5 [前端] notificationAxios.js:依 window.location.pathname 動態選擇 token/coach 開頭優先 coach_token,其餘優先 token),修正雙 token 環境下通知 API 用錯帳號的問題
  • 10.6 [前端] NotificationDrawer.vueclickItem() 改用 new URL(action_url).pathname 提取路徑,取代原本 replace(window.location.origin, '') 的不穩定做法