Include supplementary documentation, research notes on Lexical/UX, and setup guides.
23 KiB
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:
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)
完整欄位定義:
// 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:
// 在 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:
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
新增欄位定義:
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:
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
新增欄位定義:
// 在 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:
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
新增欄位定義:
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:
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
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
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:
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
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
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
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
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
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
import { adminOnly } from '../access/adminOnly'
export const Header: GlobalConfig = {
slug: 'header',
access: {
read: anyone, // ✅ 公開可讀
update: adminOnly, // ❌ 僅 Admin 可編輯
},
// ...
}
修改檔案 7: apps/backend/src/Footer/config.ts
import { adminOnly } from '../access/adminOnly'
export const Footer: GlobalConfig = {
slug: 'footer',
access: {
read: anyone, // ✅ 公開可讀
update: adminOnly, // ❌ 僅 Admin 可編輯
},
// ...
}
預估時間: 30 分鐘
Task 1.2.7: 驗證和測試
User Story:
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
測試腳本:
// 測試檔案: 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)
更新後的依賴:
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)更新:
+ 從 Payload API 載入已發布文章(status: 'published')
+ 顯示 category badge(使用 category.textColor/backgroundColor)
+ 顯示文章摘要(使用 post.excerpt)
Task 1.9.4(Category Page)更新:
+ 分類顏色主題(使用 category.textColor/backgroundColor)
+ Badge 使用動態顏色
Story 1.10: Portfolio(依賴 Story 1.2)
更新後的依賴:
Story 1.2 must be completed first:
- Portfolio collection must exist
- All 7 fields must be defined
Task 1.10.1(Design Portfolio Architecture)更新:
- 決定是否使用 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)更新:
+ 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)
更新後的依賴:
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)更新:
- 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 完整性
- Portfolio: 7/7 欄位 ✅
- Categories: 6/6 欄位 ✅
- Posts: 13/13 欄位 ✅
- Users: 4/4 欄位 ✅
- Media: 100% ✅(已完整)
- Pages: 100% ✅(已完整)
Access Control 完整性
- 5 個 access 函數 ✅
- anyone ✅
- authenticated ✅
- authenticatedOrPublished ✅
- adminOnly ✅(新增)
- adminOrEditor ✅(新增)
功能驗收
- 所有 collections 在 admin panel 可見
- 所有欄位可編輯
- 角色權限正確執行
- Media 上傳到 R2
- Rich text editor 正常
- 型別生成成功
📝 備註
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.mddocs/prd/epic-1-stories-1.3-1.17-tasks.mddocs/prd/payload-cms-slimming-report.md