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

23 KiB
Raw Blame History

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.2Blog Listing Page更新

+ 從 Payload API 載入已發布文章status: 'published'
+ 顯示 category badge使用 category.textColor/backgroundColor
+ 顯示文章摘要(使用 post.excerpt

Task 1.9.4Category 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.1Design Portfolio Architecture更新

- 決定是否使用 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更新

+ 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.5Implement 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.md
  • docs/prd/epic-1-stories-1.3-1.17-tasks.md
  • docs/prd/payload-cms-slimming-report.md