81a9f84b26
後端: - 新增 reviews / review_edits / review_votes migration(含索引) - Review / ReviewEdit / ReviewVote Model - ReviewController:評價 CRUD、資格驗證(completed booking)、rating 即時重算 - toggleHelpful:Member 限定、GREATEST 原子防負、DB transaction 同步 - AdminReviewController:全量列表、刪除(含重算) - AdminBookingController:全量列表、手動標記 completed - ProviderBookingController 新增 complete 方法(教練手動完成預約) - DevelopmentSeeder:快速重建測試資料(admin/coach/member + offers + bookings) - EnsureAdmin middleware 正式納入 bootstrap/app.php - Nginx server_name 加入 cfdive.local 前端: - 課程詳情頁加入評價區塊(星等分布、排序切換、撰寫/修改/刪除、有幫助 Toggle) - Coach Portal 新增「課程評價」頁(只讀,依課程分組) - Coach 預約管理加入「完成」按鈕 - Admin 新增「預約管理」頁(標記完成)、「評價管理」頁(刪除) - Admin / Coach Navbar 新增對應連結 OpenSpec: - review-system change 歸檔至 archive/2026-05-12-review-system - 新增 specs/review-lifecycle 與 specs/review-voting 主規格 - review-voting spec 補充 Member 限定與 GREATEST 原子更新說明 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
5.9 KiB
5.9 KiB
1. 資料庫層
- 1.1 [後端] 建立 Migration
create_reviews_table:欄位含diving_offer_id、member_id、rating(tinyint)、comment(text)、helpful_count(int)、is_edited(boolean);UNIQUE(member_id, diving_offer_id);加索引(diving_offer_id, helpful_count)、(diving_offer_id, rating)、(diving_offer_id, created_at) - 1.2 [後端] 建立 Migration
create_review_edits_table:欄位含review_id(UNIQUE FK)、old_rating、old_comment、edited_at - 1.3 [後端] 建立 Migration
create_review_votes_table:欄位含review_id、member_id;UNIQUE(review_id, member_id) - 1.4 [後端] 執行 Migration,確認三張資料表與索引正確
2. Model 層
- 2.1 [後端] 建立
app/Models/Review.php:fillable、casts、關聯(belongsTo DivingOffer / belongsTo User as member、hasOne ReviewEdit、hasMany ReviewVote) - 2.2 [後端] 建立
app/Models/ReviewEdit.php:fillable、belongsTo Review - 2.3 [後端] 建立
app/Models/ReviewVote.php:fillable、belongsTo Review / belongsTo User as member - 2.4 [後端] 在
DivingOfferModel 新增hasMany Review關聯
3. Member 評價 API
- 3.1 [後端] 建立
app/Http/Controllers/API/ReviewController.php:- 私有方法
recalculateOfferRating(int $offerId):重算 AVG(rating) 與 COUNT(*),並 UPDATE diving_offers,必須在 DB transaction 內被呼叫 store:資格驗證(bookings JOIN course_schedules WHERE status=completed AND diving_offer_id=X,否則 403)→ 重複評價檢查(422)→ DB transaction 建立 Review + 呼叫 recalculateupdate:所有權驗證(他人 403)→ DB transaction:updateOrCreate review_edits(覆蓋舊版)→ 更新 Review 內容 + is_edited=true → 呼叫 recalculatedestroy:所有權驗證(他人 403)→ DB transaction:刪除 Review(cascade edits/votes)→ 呼叫 recalculate
- 私有方法
- 3.2 [後端] 在
routes/api.php新增/member/reviews路由群組(POST / PUT /{id} / DELETE /{id})
4. 有幫助投票 API
- 4.1 [後端] 在
ReviewController新增toggleHelpful方法:不可投自己(422)→ 整個 toggle 在 DB transaction 內:查 ReviewVote → 有則 delete +DB::raw('GREATEST(helpful_count - 1, 0)')原子更新(禁止兩段式 decrement+check);無則 create + increment - 4.2 [後端] 在
routes/api.php新增POST /reviews/{id}/helpful路由(auth:sanctum)
5. 公開評價列表 API
- 5.1 [後端] 在
ReviewController新增publicList方法:- 回傳
summary(AVG、COUNT、1–5 星分布):分布用GROUP BY rating COUNT(*)動態查詢並補齊 key 1–5(含零值),不另存欄位 - 依 sort 參數排序:helpful→
(helpful_count DESC, created_at DESC);rating→(rating DESC, created_at DESC);newest→(created_at DESC) - 批次查詢 has_voted(已登入:
ReviewVote::whereIn('review_id', ...)->pluck('review_id');未登入:全 false) - is_mine:已登入才加此欄位(未登入省略)
- reviewer_name 固定為「匿名潛水者」
- 回傳
- 5.2 [後端] 在
routes/api.php新增GET /diving-offers/{id}/reviews公開路由
6. Admin 評價管理 API
- 6.1 [後端] 建立
app/Http/Controllers/API/AdminReviewController.php:index:全量列出(created_at DESC)含課程名、member email、rating、comment 前 50 字destroy:DB transaction 刪除 Review(cascade)→ 呼叫recalculateOfferRating(Admin 刪除也必須重算,與 Member destroy 共用同一邏輯)
- 6.2 [後端] 在
routes/api.phpAdmin 群組新增/admin/reviews路由(GET / DELETE /{id})
7. 前端 API 封裝
- 7.1 [前端] 建立
frontend/src/api/reviewApi.js:getReviews(offerId, sort)、createReview(payload)、updateReview(id, payload)、deleteReview(id)、toggleHelpful(reviewId)
8. 課程詳情頁評價區塊
- 8.1 [前端] 更新
frontend/src/views/CourseDetailView.vue:新增評價區塊,顯示整體星等、評分分布條、排序切換按鈕(最多幫助 / 最高分 / 最新) - 8.2 [前端] 評價列表元件:顯示星等、「匿名潛水者」、日期、「已修改」標記、「有幫助 N 人」按鈕(登入後可點擊 Toggle)
- 8.3 [前端] 評價表單:已登入 Member 且有 completed booking 才顯示;已評過則顯示「我的評價」含修改/刪除按鈕
9. Admin 評價管理頁
- 9.1 [前端] 新增
frontend/src/views/admin/ReviewsView.vue:列出所有評價(課程名、內容、星等、刪除按鈕) - 9.2 [前端] 在 Admin Navbar 加入「評價管理」連結,路由
/admin/reviews - 9.3 [前端] 在
frontend/src/router/index.js新增/admin/reviews路由(requiresAdmin)
10. 整合驗證
- 10.1 [整合測試] 完整流程:Member 完成課程 → 新增評價 → 確認 diving_offers.rating / reviews 更新
- 10.2 [整合測試] 修改評價:確認 is_edited=true、review_edits 有舊版、rating 重算正確
- 10.3 [整合測試] 刪除評價:rating/reviews 歸零或重算正確
- 10.4 [整合測試] 投票 Toggle:連點兩次確認 helpful_count 正確增減、第三次確認不低於 0
- 10.5 [整合測試] 不可投自己:Member 對自己評價投票應回傳 422
- 10.6 [整合測試] 匿名確認:API 回傳的 reviewer_name 一律為「匿名潛水者」,不含真實姓名
- 10.7 [整合測試] 排序確認:三種 sort 參數回傳順序正確
- 10.8 [整合測試] Admin 刪除重算:Admin 刪除評價後確認 diving_offers.rating / reviews 同步更新
- 10.9 [整合測試] is_mine / has_voted 欄位規則:未登入不含 is_mine 欄位;登入後自己的評價 is_mine=true;has_voted 正確反映投票狀態
- 10.10 [整合測試] 資格驗證:無 completed booking 的 Member 嘗試評價應回傳 403;有 completed booking 才能成功