Files
website-enchun-mgr/docs/prd/payload-cms-modification-plan.md
pkupuk f6b806617e docs: add research assets, screenshots and guides
Include supplementary documentation, research notes on Lexical/UX, and setup guides.
2026-02-11 11:51:35 +08:00

914 lines
23 KiB
Markdown
Raw 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.

# 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.2Blog Listing Page更新**
```diff
+ 從 Payload API 載入已發布文章status: 'published'
+ 顯示 category badge使用 category.textColor/backgroundColor
+ 顯示文章摘要(使用 post.excerpt
```
**Task 1.9.4Category 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.1Design Portfolio Architecture更新**
```diff
- 決定是否使用 Payload CMS collection
+ Portfolio collection 已在 Story 1.2 創建
- 欄位規劃
+ 欄位已在 Story 1.2 定義title, slug, url, image, description, websiteType, tags
```
**Task 1.10.2Implement 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.5Implement 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`