Files
a620906209 4baa4cb52b feat:實作課程圖片上傳 — 封面 + 相簿管理
後端:
- Migration:diving_offers 新增 cover_image 欄位、新增 course_images 表(含索引)
- CourseImage Model(CREATED_AT、url accessor)
- DivingOffer:cover_image_url accessor、hasMany courseImages、static::deleting() 孤兒清理
- CourseImageController:封面上傳/刪除、相簿上傳(max 3)/刪除,統一 mimes+size 驗證
- DivingOfferController:index/show 回傳加入 cover_image_url 與 images 陣列
- 修正 APP_URL 加入 port(:8080),Storage::url() 才能產生正確圖片連結

前端:
- courseImageApi.js:uploadCover/deleteCover/uploadImage/deleteImage
- CourseCard:有封面顯示 <img>,無封面顯示漸層佔位
- CourseDetailView:封面大圖 + 相簿縮圖橫列(點擊開新分頁)
- OfferFormView(編輯模式):封面預覽/更換/刪除、相簿縮圖管理(達 3 張隱藏上傳按鈕)

基礎設施:
- docker-entrypoint.sh:加入 storage:link --force
- docker-compose.yml:移除 storage-data named volume(改用 bind mount,避免 Nginx 讀不到圖片)

測試:
- CourseImageTest.php:14 個 Feature Test 全部 PASS(Storage::fake)
  涵蓋:上傳成功/格式驗證/大小驗證/所有權、刪除/無封面不報錯、
        相簿上限/sort_order 遞增、孤兒清理

OpenSpec:
- course-images change 歸檔至 archive/2026-05-12-course-images
- 新增 specs/course-image-upload 主規格(含 bind mount 持久化說明)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-12 03:54:45 +08:00

42 lines
2.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
目前課程頁面以 🤿 emoji 作為佔位,缺乏視覺吸引力。課程圖片是潛水平台的核心體驗,直接影響會員瀏覽意願與教練品牌形象。
## What Changes
- 新增 `diving_offers.cover_image` 欄位:課程封面,顯示於課程卡與詳情頁頂部
- 新增 `course_images` 資料表:相簿最多 3 張,顯示於課程詳情頁
- 新增 Provider API:上傳/刪除封面、上傳/刪除相簿圖片
- 更新前端:CourseCard 顯示封面、CourseDetailView 顯示封面 + 相簿、OfferFormView 加入圖片管理 UI
- Docker volume 掛載 `storage/app/public`,圖片跨 build 持久化
- `php artisan storage:link` 整合至 entrypoint,確保 symlink 正確
## Capabilities
### New Capabilities
- `course-image-upload`:Provider 上傳課程封面與相簿圖片(本地 public disk、max 2MB、支援 jpg/jpeg/png/webp)、相簿上限 3 張、刪除圖片同步移除實體檔案
### Modified Capabilities
- `coach-offers-api`:新增圖片上傳/刪除端點,`DivingOffer` 回應加入 `cover_image_url``images`
## Impact
**後端**
- Migration`diving_offers``cover_image`nullable string
- Migration:新增 `course_images`id、diving_offer_id、image_path、sort_order
- Model`CourseImage``DivingOffer``hasMany CourseImage``cover_image_url` accessor
- Controller`CourseImageController`(上傳封面、刪除封面、上傳相簿、刪除相簿)
- Route`/provider/offers/{id}/cover`POST/DELETE)、`/provider/offers/{id}/images`POST)、`/provider/images/{id}`DELETE
**前端**
- `frontend/src/api/courseImageApi.js`
- `CourseCard.vue`:有封面顯示圖片,無封面顯示漸層佔位
- `CourseDetailView.vue`:頂部大圖(封面)+ 相簿縮圖列
- `OfferFormView.vue`(或新建 `OfferImageManager.vue`):封面上傳預覽、相簿管理
**Docker**
- `docker-compose.yml` 新增 named volume `storage-data` 掛載 `/var/www/storage/app/public`
- `docker-entrypoint.sh` 加入 `php artisan storage:link --force`