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>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
## 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`
|
||||
Reference in New Issue
Block a user