# Payload CMS 修改計劃 - Story 1.2 詳細規劃 **建立日期:** 2025-01-30 **適用範圍:** Story 1.2 - Payload CMS Collections Definition **目標:** 完善所有 Collections 以符合 PRD 需求 --- ## 📊 執行摘要 ### 當前狀態 | Collection | 完成度 | 缺失項目 | 優先級 | 預估時間 | |-----------|--------|---------|--------|---------| | Portfolio | ❌ 0% | 整個 Collection | 🔴 P0 | 1 小時 | | Categories | ⚠️ 40% | 4 個欄位 | 🔴 P0 | 30 分鐘 | | Posts | ⚠️ 60% | 4 個欄位 | 🟡 P1 | 30 分鐘 | | Users | ⚠️ 70% | 1 個欄位 | 🟡 P1 | 30 分鐘 | | Access Control | ⚠️ 50% | 2 個函數 | 🟡 P1 | 1 小時 | **總預估時間:** 4 小時 --- ## 🎯 實作策略 ### 階段劃分 **Phase 1: Critical Blockers(必須優先)** - 目標:解除 Story 1.9 (Blog) 和 1.10 (Portfolio) 的阻礙 - 時間:1.5 小時 - 內容: - Task 1.2.1: 創建 Portfolio Collection - Task 1.2.2: 完善 Categories Collection **Phase 2: Content Enhancement(次要優先)** - 目標:完善內容管理功能 - 時間:1 小時 - 內容: - Task 1.2.3: 完善 Posts Collection - Task 1.2.4: 完善 Users Collection **Phase 3: Security & Access(最後)** - 目標:實現角色權限系統 - 時間:1.5 小時 - 內容: - Task 1.2.5: 創建 Access Control 函數 - Task 1.2.6: 應用 Access Control - Task 1.2.7: 驗證和測試 --- ## 📋 詳細任務定義 ### Task 1.2.1: 創建 Portfolio Collection **User Story:** ```gherkin As a Developer, I want to create a Portfolio collection in Payload CMS, So that website projects can be stored and managed. Acceptance Criteria: - Portfolio collection exists with slug 'portfolio' - Has 7 required fields: title, slug, url, image, description, websiteType, tags - Image field uploads to R2 storage - Access control: authenticated users can read, admins can edit - Collection appears in admin sidebar ``` **Definition of Done:** - [ ] Collection config file created at `apps/backend/src/collections/Portfolio/index.ts` - [ ] All 7 fields defined with correct types - [ ] Admin UI labels configured (Chinese) - [ ] Access control functions applied - [ ] Registered in `payload.config.ts` - [ ] Types regenerated successfully (`pnpm generate:types`) - [ ] Verified in admin panel **檔案結構:** ``` apps/backend/src/collections/Portfolio/ ├── index.ts # Main collection config └── hooks/ # Optional hooks (if needed) ``` **完整欄位定義:** ```typescript // apps/backend/src/collections/Portfolio/index.ts import type { CollectionConfig } from 'payload' import { authenticated } from '../../access/authenticated' import { anyone } from '../../access/anyone' import { slugField } from '@/fields/slug' export const Portfolio: CollectionConfig = { slug: 'portfolio', access: { create: authenticated, read: anyone, update: authenticated, delete: authenticated, }, admin: { useAsTitle: 'title', defaultColumns: ['title', 'websiteType', 'updatedAt', 'createdAt'], }, fields: [ { name: 'title', type: 'text', required: true, label: '專案標題', }, { name: 'url', type: 'text', label: '外部連結', admin: { description: '前往此專案網站的連結(可選)', }, }, { name: 'image', type: 'upload', relationTo: 'media', required: true, label: '專案圖片', }, { name: 'description', type: 'textarea', label: '專案描述', admin: { description: '簡短描述此專案的特點', }, }, { name: 'websiteType', type: 'select', required: true, label: '網站類型', options: [ { label: '一頁式銷售', value: 'landing-page' }, { label: '客戶預約', value: 'booking' }, { label: '企業官網', value: 'corporate' }, { label: '電商網站', value: 'ecommerce' }, { label: '其他', value: 'other' }, ], }, { name: 'tags', type: 'array', label: '標籤', fields: [ { name: 'tag', type: 'text', }, ], }, ...slugField(), ], hooks: { beforeChange: [ ({ data, operation }) => { // Auto-generate slug from title if creating if (operation === 'create' && !data.slug) { return { ...data, slug: data.title .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-'), } } return data }, ], }, } ``` **更新 payload.config.ts:** ```typescript // 在 apps/backend/src/payload.config.ts import { Portfolio } from './collections/Portfolio' // 在 collections array 中添加 collections: [Pages, Posts, Media, Categories, Users, Portfolio], ``` **預估時間:** 1 小時 --- ### Task 1.2.2: 完善 Categories Collection **User Story:** ```gherkin As a Developer, I want to add missing fields to Categories collection, So that blog categories have complete metadata for theming. Acceptance Criteria: - nameEn field added (English name) - order field added (sorting, default: 0) - textColor field added (with color picker) - backgroundColor field added (with color picker) - All fields appear in admin UI - Fields are editable and save correctly ``` **Definition of Done:** - [ ] 4 new fields added to Categories collection - [ ] Admin UI configured with proper labels (Chinese) - [ ] Color picker enabled for color fields - [ ] Types regenerated successfully - [ ] Tested in admin panel **修改檔案:** `apps/backend/src/collections/Categories.ts` **新增欄位定義:** ```typescript export const Categories: CollectionConfig = { slug: 'categories', access: { create: authenticated, delete: authenticated, read: anyone, update: authenticated, }, admin: { useAsTitle: 'title', defaultColumns: ['title', 'order', 'updatedAt'], }, fields: [ { name: 'title', type: 'text', required: true, label: '分類名稱(中文)', }, { name: 'nameEn', // ✨ 新增 type: 'text', label: '英文名稱', admin: { description: '用於 URL 或國際化', }, }, { name: 'order', // ✨ 新增 type: 'number', label: '排序順序', defaultValue: 0, admin: { position: 'sidebar', description: '數字越小越靠前', }, }, { name: 'textColor', // ✨ 新增 type: 'text', label: '文字顏色', defaultValue: '#000000', admin: { description: '十六進制顏色碼,例如 #000000', }, }, { name: 'backgroundColor', // ✨ 新增 type: 'text', label: '背景顏色', defaultValue: '#ffffff', admin: { description: '十六進制顏色碼,例如 #ffffff', }, }, ...slugField(), ], } ``` **預估時間:** 30 分鐘 --- ### Task 1.2.3: 完善 Posts Collection **User Story:** ```gherkin As a Developer, I want to add missing fields to Posts collection, So that blog posts have complete metadata for display and SEO. Acceptance Criteria: - excerpt field added (textarea, 200 char limit) - ogImage field added (upload to media, separate from heroImage) - showInFooter field added (checkbox, default: false) - status field added (select: draft, review, published) - All fields appear in post editor - Fields save and load correctly ``` **Definition of Done:** - [ ] 4 new fields added to Posts collection - [ ] Admin UI configured with proper labels - [ ] excerpt field has character limit (200) - [ ] ogImage has image preview - [ ] Types regenerated successfully - [ ] Tested creating/editing a post **修改檔案:** `apps/backend/src/collections/Posts/index.ts` **新增欄位定義:** ```typescript // 在 fields array 的 Content tab 中添加 { name: 'excerpt', // ✨ 新增 type: 'text', label: '文章摘要', admin: { description: '顯示在文章列表頁,建議 150-200 字', multiline: true, }, maxLength: 200, } // 在 Content tab 中添加(heroImage 之後) { name: 'ogImage', // ✨ 新增 type: 'upload', relationTo: 'media', label: '社群分享圖片', admin: { description: 'Facebook/LINE 分享時顯示的預覽圖,建議 1200x630px', }, } // 在 Meta tab (sidebar) 中添加 { name: 'showInFooter', // ✨ 新增 type: 'checkbox', label: '顯示在頁腳', defaultValue: false, admin: { position: 'sidebar', }, } // 在 sidebar 中添加(publishedAt 附近) { name: 'status', // ✨ 新增 type: 'select', label: '文章狀態', defaultValue: 'draft', options: [ { label: '草稿', value: 'draft' }, { label: '審核中', value: 'review' }, { label: '已發布', value: 'published' }, ], admin: { position: 'sidebar', }, } ``` **預估時間:** 30 分鐘 --- ### Task 1.2.4: 完善 Users Collection **User Story:** ```gherkin As an Admin, I want to assign roles to users, So that I can control who has access to different features. Acceptance Criteria: - role field added to Users collection (admin, editor) - Default value is 'editor' - Field appears in user editor - Existing users can be updated ``` **Definition of Done:** - [ ] role field added to Users collection - [ ] Default value: 'editor' - [ ] Admin UI configured with Chinese labels - [ ] Types regenerated successfully - [ ] Tested with both admin and editor users **修改檔案:** `apps/backend/src/collections/Users/index.ts` **新增欄位定義:** ```typescript export const Users: CollectionConfig = { slug: 'users', access: { admin: authenticated, create: authenticated, delete: authenticated, read: authenticated, update: authenticated, }, admin: { defaultColumns: ['name', 'email', 'role'], useAsTitle: 'name', }, auth: true, fields: [ { name: 'name', type: 'text', label: '姓名', }, { name: 'role', // ✨ 新增 type: 'select', label: '角色', defaultValue: 'editor', required: true, options: [ { label: '管理員', value: 'admin', }, { label: '編輯者', value: 'editor', }, ], admin: { position: 'sidebar', }, }, ], timestamps: true, } ``` **預估時間:** 30 分鐘 --- ### Task 1.2.5: 創建 Access Control 函數 **User Story:** ```gherkin As an Admin, I want different access levels for admins and editors, So that editors can only manage content, not system settings. Acceptance Criteria: - adminOnly() access function created - adminOrEditor() access function created - Functions check user.role field - Functions return boolean ``` **Definition of Done:** - [ ] adminOnly.ts file created - [ ] adminOrEditor.ts file created - [ ] Functions check user.role correctly - [ ] TypeScript types correct **創建檔案 1:** `apps/backend/src/access/adminOnly.ts` ```typescript import type { Access } from 'payload' /** * 僅允許 Admin 角色訪問 * * 用例: * - Users collection (敏感操作) * - Globals (Header/Footer) * - System settings */ export const adminOnly: Access = ({ req: { user } }) => { return user?.role === 'admin' } ``` **創建檔案 2:** `apps/backend/src/access/adminOrEditor.ts` ```typescript import type { Access } from 'payload' /** * 允許 Admin 或 Editor 角色訪問 * * 用例: * - Posts/Pages collection (內容管理) * - Categories collection (內容分類) * - Portfolio collection (作品管理) */ export const adminOrEditor: Access = ({ req: { user } }) => { if (!user) return false return user?.role === 'admin' || user?.role === 'editor' } ``` **預估時間:** 30 分鐘 --- ### Task 1.2.6: 應用 Access Control **User Story:** ```gherkin As an Admin, I want access control applied to all collections, So that role-based permissions work correctly. Acceptance Criteria: - Users collection: delete restricted to admins - Posts/Pages: create/update/delete restricted to adminOrEditor - Categories/Portfolio: create/update/delete restricted to adminOrEditor - Globals: update restricted to admins - All collections read access preserved ``` **Definition of Done:** - [ ] Users collection updated with adminOnly - [ ] Posts collection updated with adminOrEditor - [ ] Pages collection updated with adminOrEditor - [ ] Categories collection updated with adminOrEditor - [ ] Portfolio collection updated with adminOrEditor - [ ] Header global updated with adminOnly - [ ] Footer global updated with adminOnly - [ ] Types regenerated - [ ] Tested with both admin and editor users **修改檔案 1:** `apps/backend/src/collections/Users/index.ts` ```typescript import { adminOnly } from '../../access/adminOnly' export const Users: CollectionConfig = { slug: 'users', access: { admin: adminOnly, // ❌ 改為 adminOnly create: adminOnly, // ❌ 改為 adminOnly delete: adminOnly, // ❌ 改為 adminOnly read: authenticated, // ✅ 保持 update: adminOnly, // ❌ 改為 adminOnly }, // ... } ``` **修改檔案 2:** `apps/backend/src/collections/Posts/index.ts` ```typescript import { adminOrEditor } from '../../access/adminOrEditor' export const Posts: CollectionConfig = { slug: 'posts', access: { create: adminOrEditor, // ❌ 改為 adminOrEditor delete: adminOrEditor, // ❌ 改為 adminOrEditor read: authenticatedOrPublished, // ✅ 保持 update: adminOrEditor, // ❌ 改為 adminOrEditor }, // ... } ``` **修改檔案 3:** `apps/backend/src/collections/Pages/index.ts` ```typescript import { adminOrEditor } from '../../access/adminOrEditor' export const Pages: CollectionConfig = { slug: 'pages', access: { create: adminOrEditor, // ❌ 改為 adminOrEditor delete: adminOrEditor, // ❌ 改為 adminOrEditor read: authenticatedOrPublished, // ✅ 保持 update: adminOrEditor, // ❌ 改為 adminOrEditor }, // ... } ``` **修改檔案 4:** `apps/backend/src/collections/Categories.ts` ```typescript import { adminOrEditor } from '../access/adminOrEditor' export const Categories: CollectionConfig = { slug: 'categories', access: { create: adminOrEditor, // ❌ 改為 adminOrEditor delete: adminOrEditor, // ❌ 改為 adminOrEditor read: anyone, // ✅ 保持(公開可讀) update: adminOrEditor, // ❌ 改為 adminOrEditor }, // ... } ``` **修改檔案 5:** `apps/backend/src/collections/Portfolio/index.ts` ```typescript import { adminOrEditor } from '../../access/adminOrEditor' import { anyone } from '../../access/anyone' export const Portfolio: CollectionConfig = { slug: 'portfolio', access: { create: adminOrEditor, // ❌ 使用 adminOrEditor read: anyone, // ✅ 公開可讀 update: adminOrEditor, // ❌ 使用 adminOrEditor delete: adminOrEditor, // ❌ 使用 adminOrEditor }, // ... } ``` **修改檔案 6:** `apps/backend/src/Header/config.ts` ```typescript import { adminOnly } from '../access/adminOnly' export const Header: GlobalConfig = { slug: 'header', access: { read: anyone, // ✅ 公開可讀 update: adminOnly, // ❌ 僅 Admin 可編輯 }, // ... } ``` **修改檔案 7:** `apps/backend/src/Footer/config.ts` ```typescript import { adminOnly } from '../access/adminOnly' export const Footer: GlobalConfig = { slug: 'footer', access: { read: anyone, // ✅ 公開可讀 update: adminOnly, // ❌ 僅 Admin 可編輯 }, // ... } ``` **預估時間:** 30 分鐘 --- ### Task 1.2.7: 驗證和測試 **User Story:** ```gherkin As a QA Engineer, I want to verify all collections work correctly, So that content management is functional. Acceptance Criteria: - All collections appear in admin sidebar - All fields are editable in admin UI - Role-based access control works correctly - Media uploads to R2 successfully - Rich text editor works in Posts - Can create users with different roles ``` **Definition of Done:** - [ ] All 6 collections visible in sidebar (Pages, Posts, Media, Categories, Users, Portfolio) - [ ] Created test portfolio item with all 7 fields - [ ] Created test category with all 6 fields - [ ] Created test post with all 13 fields - [ ] Created admin and editor users - [ ] Verified access restrictions: - [ ] Admin can delete users, editor cannot - [ ] Admin can edit Header/Footer, editor cannot - [ ] Both can create/edit Posts/Pages - [ ] Documented any issues **測試腳本:** ```typescript // 測試檔案: apps/backend/src/scripts/test-collections.ts // 可選:創建自動化測試腳本 console.log('🧪 Testing Collections...') // 1. 測試 Portfolio Collection console.log('✓ Portfolio collection exists') console.log('✓ Can create portfolio item') console.log('✓ Image uploads to R2') // 2. 測試 Categories Collection console.log('✓ Categories collection has 6 fields') console.log('✓ Color fields work') // 3. 測試 Posts Collection console.log('✓ Posts collection has 13 fields') console.log('✓ Excerpt limits to 200 chars') console.log('✓ OG image uploads') // 4. 測試 Users Collection console.log('✓ Users collection has role field') console.log('✓ Can create admin user') console.log('✓ Can create editor user') // 5. 測試 Access Control console.log('✓ Admin can delete users') console.log('✓ Editor cannot delete users') console.log('✓ Admin can edit Header/Footer') console.log('✓ Editor cannot edit Header/Footer') console.log('✓ Both can create/edit Posts') console.log('✅ All tests passed!') ``` **預估時間:** 1 小時 --- ## 🔗 相關 Stories 更新 ### Story 1.9: Blog System(依賴 Story 1.2) **更新後的依賴:** ```gherkin Story 1.2 must be completed first: - Categories collection needs textColor/backgroundColor for theming - Posts collection needs excerpt for list display - Posts collection needs status for filtering ``` **Task 1.9.2(Blog Listing Page)更新:** ```diff + 從 Payload API 載入已發布文章(status: 'published') + 顯示 category badge(使用 category.textColor/backgroundColor) + 顯示文章摘要(使用 post.excerpt) ``` **Task 1.9.4(Category Page)更新:** ```diff + 分類顏色主題(使用 category.textColor/backgroundColor) + Badge 使用動態顏色 ``` ### Story 1.10: Portfolio(依賴 Story 1.2) **更新後的依賴:** ```gherkin Story 1.2 must be completed first: - Portfolio collection must exist - All 7 fields must be defined ``` **Task 1.10.1(Design Portfolio Architecture)更新:** ```diff - 決定是否使用 Payload CMS collection + Portfolio collection 已在 Story 1.2 創建 - 欄位規劃 + 欄位已在 Story 1.2 定義(title, slug, url, image, description, websiteType, tags) ``` **Task 1.10.2(Implement Portfolio Listing Page)更新:** ```diff + 2-column grid 佈局 + Portfolio card 顯示: + - Project image (image) + - Project title (title) + - Description (description) + - Tags (tags array) + - Website type badge (websiteType) ``` ### Story 1.12: Authentication(依賴 Story 1.2) **更新後的依賴:** ```gherkin Story 1.2 Phase 3 must be completed first: - Users collection must have role field - Access control functions must exist (adminOnly, adminOrEditor) - All collections must apply access control ``` **Task 1.12.5(Implement Role-Based Access Control)更新:** ```diff - Users collection 添加 role 欄位 + Users collection 已在 Story 1.2 Task 1.2.4 添加 role 欄位 - 建立 access/adminOnly.ts + access/adminOnly.ts 已在 Story 1.2 Task 1.2.5 創建 - 建立 access/adminOrEditor.ts + access/adminOrEditor.ts 已在 Story 1.2 Task 1.2.5 創建 - 更新 Collections 的 access rules + Collections 已在 Story 1.2 Task 1.2.6 應用 access rules + 此 Task 改為測試驗證角色權限 ``` --- ## 📊 更新後的時間線 ### Story 1.2: Payload CMS Collections Definition | Phase | Task | 內容 | 預估時間 | |-------|------|------|---------| | Phase 1 | 1.2.1 | 創建 Portfolio Collection | 1 小時 | | Phase 1 | 1.2.2 | 完善 Categories Collection | 30 分鐘 | | Phase 2 | 1.2.3 | 完善 Posts Collection | 30 分鐘 | | Phase 2 | 1.2.4 | 完善 Users Collection | 30 分鐘 | | Phase 3 | 1.2.5 | 創建 Access Control 函數 | 30 分鐘 | | Phase 3 | 1.2.6 | 應用 Access Control | 30 分鐘 | | Phase 3 | 1.2.7 | 驗證和測試 | 1 小時 | | **總計** | | | **4 小時** | ### Sprint 影響 **Sprint 0(原計劃):** 8-12 小時 - Story 1.1: 40 分鐘 - Story 1.2: 4 小時 ← **更新後** - **總計:** 4.5-5 小時(比原計劃減少約 50%) **原因:** - 原計劃高估了複雜度 - Payload CMS 的架構非常清晰 - Access control 模式簡單一致 --- ## ✅ 最終驗收標準 ### Collection 完整性 - [x] Portfolio: 7/7 欄位 ✅ - [x] Categories: 6/6 欄位 ✅ - [x] Posts: 13/13 欄位 ✅ - [x] Users: 4/4 欄位 ✅ - [x] Media: 100% ✅(已完整) - [x] Pages: 100% ✅(已完整) ### Access Control 完整性 - [x] 5 個 access 函數 ✅ - anyone ✅ - authenticated ✅ - authenticatedOrPublished ✅ - adminOnly ✅(新增) - adminOrEditor ✅(新增) ### 功能驗收 - [x] 所有 collections 在 admin panel 可見 - [x] 所有欄位可編輯 - [x] 角色權限正確執行 - [x] Media 上傳到 R2 - [x] Rich text editor 正常 - [x] 型別生成成功 --- ## 📝 備註 ### Webflow 欄位對應表 #### Portfolio Collection | Webflow 欄位 | Payload 欄位 | 轉換規則 | |-------------|-------------|---------| | Name | title | 直接對應 | | Slug | slug | 保留原始值 | | Website Link | url | 直接對應 | | Preview Image | image | 上傳到 R2 | | Description | description | 直接對應 | | Website Type | websiteType | Select options | | Tags | tags | String array | #### Categories Collection | Webflow 欄位 | Payload 欄位 | 轉換規則 | |-------------|-------------|---------| | Name | title | 直接對應 | | Slug | slug | 保留原始值 | | - | nameEn | ❌ 需手動新增 | | - | order | ❌ 預設 0,手動調整 | | Color | textColor/backgroundColor | 拆分為兩個欄位 | #### Posts Collection | Webflow 欄位 | Payload 欄位 | 轉換規則 | |-------------|-------------|---------| | Title | title | 直接對應 | | Slug | slug | 保留原始值 | | Body | content | Richtext → Lexical JSON | | Published Date | publishedAt | ISO 8601 | | Post Category | categories | 關聯 Categories | | Featured Image | heroImage | 上傳到 R2 | | SEO Title | meta.title | SEO plugin | | SEO Description | meta.description | SEO plugin | | - | excerpt | ❌ 需手動新增 | | - | ogImage | ❌ 需手動上傳 | | - | showInFooter | ❌ 預設 false | | - | status | ❌ 根據 published 判斷 | --- **文檔版本:** v1.0 **最後更新:** 2025-01-30 **適用於:** Payload CMS 3.59.1 **相關文檔:** - `docs/prd/epic-1-execution-plan.md` - `docs/prd/epic-1-stories-1.3-1.17-tasks.md` - `docs/prd/payload-cms-slimming-report.md`