Talaween/Payload-LFRs

GitHub: Talaween/Payload-LFRs

为 Payload CMS 3.x 提供点赞、收藏、评分和评论功能的开箱即用插件,支持细粒度权限控制、评论审核与媒体附件。

Stars: 1 | Forks: 0

# Payload LFRs 插件 一个功能全面的 [Payload CMS 3.x](https://payloadcms.com) 插件,为现有集合添加 **Likes**、**Favourites**、**Ratings** 和 **Reviews** (LFRs) 功能。 ## 功能 - **Likes & Dislikes**:允许用户对文档进行 like 或 dislike。Dislike 与 like 互斥。 - **Favourites**:允许用户将文档保存到收藏夹。 - **Ratings**:添加可自定义的评分系统(例如,5星、10分制、半星)。 - **Reviews & Replies**:允许用户撰写评论,其他人可以进行回复。 - **Review Media**:用户可以在评论中附加图片或视频 - **Admin Moderation**:审核视图,用于批准或删除评论和回复 - **Extensible API**:Headless REST API,提供完全的前端灵活性 ## 截图 以下是使用此插件在前端构建的示例: ### 交互(Likes、Dislikes、Favourites) ![交互界面](https://static.pigsec.cn/wp-content/uploads/repos/cas/b7/b7de4351a6187497816551b131de156cc0aa8d66784eb315b577ef5dea3dd987.png) ### 评分分布与提交 ![评分界面](https://static.pigsec.cn/wp-content/uploads/repos/cas/78/785be09eb468ea967c21f7d9a9a4fa03572f69fa2a489fe06b878c7560ed0736.png) ### 评论与嵌套回复 ![评论界面](https://static.pigsec.cn/wp-content/uploads/repos/cas/ac/ac144f691cc1c94834f44bdfb94ff097cd0acc1971b4e3dfd9ae40601987f256.png) ## 安装 ``` npm install payload-lfrs # 或 pnpm add payload-lfrs # 或 yarn add payload-lfrs ``` ## 示例项目 本仓库包含一个功能完整的 Next.js 示例项目,位于 `dev` 文件夹中。它演示了如何将插件集成到 Payload 配置中,以及如何在前端应用中使用提供的 React 组件。 要在本地运行示例项目: ``` git clone https://github.com/Talaween/Payload-LFRs.git cd Payload-LFRs pnpm install pnpm dev ``` 示例应用将在 `http://localhost:3000` 提供。 ## 基本用法 将插件添加到您的 Payload 配置中。以下是展示所有可用配置选项的综合示例: ``` import { buildConfig } from 'payload' import { payloadLFRs } from 'payload-lfrs' export default buildConfig({ // ... your existing config plugins: [ payloadLFRs({ collections: { // Target collection slug posts: { likes: true, // Enable likes for authenticated users dislikes: true, // Enable dislikes (mutually exclusive with likes) favourites: true, // Enable favourites ratings: true, // Enable ratings reviews: true, // Enable reviews replies: ['admin'], // Enable replies, but restrict to admin roles readReviews: 'public', // Access control for reading reviews ('public', true, or roles array) allowMultipleReviews: false, // Prevent users from submitting multiple reviews on the same doc enableReviewRating: true, // Force users to choose a rating score when reviewing }, }, // Configure global rating options rating: { max: 5, // Max rating scale value (default: 5) step: 0.5, // Value increment steps (default: 1) icon: 'star', // Icon identifier hint for frontend (default: 'star') }, // Enable review media uploads (requires an existing upload-enabled collection) reviewMedia: { uploadCollection: 'media', allowedMimeTypes: ['image/*'], maxFiles: 5, maxFileSize: 5 * 1024 * 1024, // 5MB }, reviewModeration: true, // Require reviews to be approved before they are public (default: false) adminControls: true, // Set to false to hide the Global Settings from the Admin UI adminGroup: 'LFRs', // Navigation group name in the Admin panel (default: 'LFRs') usersCollectionSlug: 'users', // Slug of your auth collection (default: 'users') }), ], }) ``` ## 配置 `payloadLFRs` 插件接受一个包含以下属性的配置对象: ### `collections` (必填) 一个集合 slug 映射,用于启用 LFRs 功能。对于每个集合,您可以启用特定功能并配置访问控制。 ``` collections: { posts: { likes: true, // Enable likes for any authenticated user dislikes: false, // Disabled favourites: ['admin', 'subscriber'], // Only specific roles can favourite ratings: true, reviews: true, readReviews: 'public', // Set who can read reviews allowMultipleReviews: true, // Allow users to leave multiple reviews (default: false) enableReviewRating: false, // Make review ratings optional for comment-style reviews (default: true) replies: ['admin'], // Enable replies, but only admins can respond } } ``` ### `readReviews` 用于查看评论和回复的访问控制。 与默认为 `true`(需要身份验证)的交互功能不同,它默认为 `'public'`,允许任何人(包括访客)阅读评论和回复。您可以将其限制为特定角色(例如 `['admin']`)、仅限登录用户(`true`),或者提供一个自定义函数。 _(默认值:`'public'`)_ ### `allowMultipleReviews` 如果为 `true`,用户可以对同一个文档提交多条评论。如果为 `false`,则限制为只能提交一条评论,并且 UI 组件将显示“编辑评论”按钮而不是“撰写评论”。 _(默认值:`false`)_ ### `enableReviewRating` 如果为 `false`,用户可以在不强制提供星级评分的情况下提交评论。这有效地将评论系统转变为标准的评论/留言系统。 _(默认值:`true`)_ #### 访问控制 对于每个功能(`likes`、`dislikes`、`favourites`、`ratings`、`reviews`、`replies`),您可以提供: - `true`:任何经过身份验证的用户都可以使用该功能(如果省略了功能键但提到了该功能,则为默认值,具体取决于实现/类型默认值)。 - `false`:此集合禁用该功能。 - `string[]`:只有 `roles` 数组中至少包含其中一个角色才能使用该功能。例如,`replies: ['admin']` 将回复限制为仅限管理员。 - `Function`:一个自定义的异步函数,接收请求和目标文档。返回 `true` 表示允许,返回 `false` 表示拒绝。 ``` likes: async ({ req, targetCollection, targetDoc }) => { // Custom logic: e.g., only users who purchased this product can review it return true } ``` ### `rating` 配置评分系统(默认:5星制,整数)。 ``` rating: { max: 5, // Maximum rating value (default: 5) step: 0.5, // Step increment, e.g., 0.5 for half-stars (default: 1) icon: 'star', // Icon identifier hint for frontend (default: 'star') } ``` ### `reviewMedia` 允许用户在评论中附加媒体。**注意:** 您必须提供已启用上传功能的现有集合的 slug。 ``` reviewMedia: { uploadCollection: 'media', // REQUIRED: an existing upload collection in your payload config allowedMimeTypes: ['image/jpeg', 'image/png'], // default: ['image/*'] maxFiles: 3, // default: 5 maxFileSize: 5 * 1024 * 1024, // 5MB limit } ``` ### `reviewModeration` 设置为 `true` 则要求评论在公开可见之前必须获得批准(默认值:`false`)。这还将在管理面板中添加一个专门的评论审核视图。 ``` reviewModeration: true ``` ### `usersCollectionSlug` 用于身份验证的用户集合的 slug(默认值:`'users'`)。 ### `adminGroup` LFRs 集合在 Admin UI 中显示的组名称(默认值:`'LFRs'`)。 ### `adminControls` 设置为 `false` 可从 Payload Admin 面板中隐藏动态全局设置页面(`LFRs Settings`)(默认值:`true`)。这可以防止管理员在 runtime 动态覆盖插件的配置,同时保持对交互集合的访问。 ### `disabled` 设置为 `true` 可完全禁用插件的功能,而无需卸载它或丢失数据(默认值:`false`)。 当 `disabled: true` 时,插件将继续注册其集合和字段以保持数据库 schema 的一致性(这对于 migrations 很重要),但它 _不会_ 注册任何 API endpoints、lifecycle hooks 或 Admin UI 组件。这非常适合在保持历史数据完整的同时暂时暂停交互。 ### `collectionSlugs` 覆盖插件创建的内部集合(`likes`、`dislikes`、`favourites`、`ratings`、`reviews`、`replies`)的默认 slug。 ### Admin UI Runtime 控制 (`LfrsSettings`) 插件会自动在管理面板中生成一个名为 `LFRs Settings` 的 **Payload Global**。这允许管理员在不更改代码或重启服务器的情况下,动态临时启用/禁用功能。 **重要提示:** 管理控制严格根据开发者的静态配置(`payload.config.ts`)生成。 - 如果开发者在代码中明确将某个功能(如 `Reviews`)设置为 `false`,管理员 **无法** 将其开启。 - 管理员覆盖操作(例如,在受到垃圾信息攻击时关闭审核或禁用 Likes)会立即与前端的 UI 同步,并且 REST API 会安全地阻止所有相关的 mutations 操作。 ### `callbacks` Hook 到用户交互和审核状态更改中,以触发自定义业务逻辑(例如,发送电子邮件通知、奖励积分、与外部系统同步)。所有 callbacks 都可以是异步的。 ``` plugins: [ payloadLFRs({ // ... callbacks: { onReviewSubmitted: async ({ req, review }) => { // Triggered when a user creates or edits a review console.log(`Review submitted by user ${req.user.id}`) }, onReviewStateChanged: async ({ req, review, previousStatus }) => { // Triggered when an admin changes a review's moderation status if (review.status === 'approved' && previousStatus !== 'approved') { // Send a notification email to the author } }, onLiked: async ({ req, like }) => { // Example: Award points to the target document's author }, }, }), ] ``` **可用的 Callbacks(均接收 `PayloadRequest` 对象以及相关上下文):** - **`onReviewSubmitted`**:`{ req, review }` — 在用户创建或更新评论时触发。 - **`onReviewDeleted`**:`{ req, reviewId, targetCollection, targetDoc }` — 在用户删除其评论时触发。 - **`onReplySubmitted`**:`{ req, reply }` — 在用户创建或更新回复时触发。 - **`onReviewStateChanged`**:`{ req, review, previousStatus }` — 在管理员通过 Admin 面板更改评论的 `status` 时触发。 - **`onRatingSubmitted`**:`{ req, rating }` — 在用户提交全新的独立评分时触发。 - **`onRatingUpdated`**:`{ req, rating }` — 在用户更新其现有评分时触发。 - **`onLiked`**:`{ req, like }` — 在用户 like 一篇文档时触发。 - **`onUnliked`**:`{ req, targetCollection, targetDoc }` — 在用户移除其 like 时触发。 - **`onDisliked`**:`{ req, dislike }` — 在用户 dislike 一篇文档时触发。 - **`onUndisliked`**:`{ req, targetCollection, targetDoc }` — 在用户移除其 dislike 时触发。 ## 工作原理 1. **添加 Collections**:插件自动创建集合以存储交互(例如 `lfrs_likes`、`lfrs_reviews`)。 2. **注入 Fields**:它将一个 `lfrs` 字段组注入到您的目标集合中,其中包含聚合数据(例如 `lfrs.likesCount`、`lfrs.averageRating`)。 3. **创建 Endpoints**:它在 `/api/lfrs/...` 下注册 REST endpoints 以处理交互(例如 `/api/lfrs/like`、`/api/lfrs/rate`)。 4. **Admin UI**:将自定义组件和审核视图添加到 Payload Admin 面板。 ### 交互状态小部件 对于启用了 LFRs 功能的每个目标集合,插件会将一个自定义的 **交互状态小部件** 注入到文档的编辑视图侧边栏中。此小部件显示该文档所有聚合交互的快速摘要(例如总 likes 数、总评论数和平均评分)。 ### 评论审核视图 如果在您的配置中启用了 `reviewModeration: true`,插件将在管理面板中提供一个专门的 **评论审核队列** 视图。通过 `/admin/lfrs-moderation` 访问,此仪表板允许管理员在待处理用户评论和回复公开展示之前,对其进行高效的审查、批准或拒绝。 ## API Endpoints 插件公开了多个 endpoints,供您的前端与 LFRs 功能进行交互: - `POST /api/lfrs/like` — 切换用户对文档的 like 状态。 - **Body:** `{ collection: string, id: string }` - **返回:** `{ liked: boolean, likesCount: number, disliked?: boolean, dislikesCount?: number }` - `POST /api/lfrs/dislike` — 切换用户对文档的 dislike 状态。 - **Body:** `{ collection: string, id: string }` - **返回:** `{ disliked: boolean, dislikesCount: number, liked?: boolean, likesCount?: number }` - `POST /api/lfrs/favourite` — 切换用户对文档的收藏状态。 - **Body:** `{ collection: string, id: string }` - **返回:** `{ favourited: boolean, favouritesCount: number }` - `POST /api/lfrs/rate` — 提交或更新用户的评分。 - **Body:** `{ collection: string, id: string, score: number }` - **返回:** `{ rating: object, ratingConfig: object, ratingsAverage: number, ratingsCount: number }` - `POST /api/lfrs/review` — 提交或更新用户评论。 - **Body:** `{ collection: string, id: string, body: string, title?: string, score?: number, media?: string[], reviewId?: string }` - **返回:** `{ review: object, reviewsCount: number }` - `DELETE /api/lfrs/review` — 删除用户的评论。 - **Body:** `{ reviewId: string }` - **返回:** `{ deleted: true, reviewsCount: number }` - `POST /api/lfrs/reply` — 提交或更新对评论的回复。 - **Body:** `{ body: string, reviewId: string, replyId?: string }` - **返回:** `{ reply: object, repliesCount: number }` - `DELETE /api/lfrs/reply` — 删除回复。 - **Body:** `{ replyId: string }` - **返回:** `{ deleted: true, repliesCount: number }` - `GET /api/lfrs/status` — 返回功能配置标志和当前用户的交互状态。 - **Query:** `collection` (必填), `id` (必填) - **返回:** `{ likesCount: number, dislikesCount: number, liked: boolean, favourited: boolean, rating: number | null, review: object | null, ... }` - `GET /api/lfrs/interactions` — 获取分页的评分或评论列表。 - **Query:** `collection` (必填), `id` (必填), `type` (可选:`'reviews'` | `'ratings'`,默认为 `'reviews'`), `page` (可选), `limit` (可选), `sort` (可选:`'newest'` | `'oldest'` | `'highest'` | `'lowest'`) - **返回:** `{ docs: Array, page: number, totalDocs: number, totalPages: number }` - `GET /api/lfrs/distribution` — 返回评分的分布频率。 - **Query:** `collection` (必填), `id` (必填) - **返回:** `{ averageScore: number, totalRatings: number, distribution: Array<{ score: number, count: number, percentage: number }> }` - `GET /api/lfrs/user-favourites` — 返回用户收藏的文档 ID。 - **Query:** `collection` (必填), `userId` (必填), `limit` (可选) - **返回:** `{ ids: string[] }` - `GET /api/lfrs/user-reviews` — 获取特定用户对某文档的评论。 - **Query:** `collection` (必填), `id` (必填), `userId` (必填) - **返回:** `{ reviews: Array }` - `GET /api/lfrs/likes-count` — 获取总 likes 数。 - **Query:** `collection` (必填), `id` (必填) - **返回:** `{ likesCount: }` - `GET /api/lfrs/dislikes-count` — 获取总 dislikes 数。 - **Query:** `collection` (必填), `id` (必填) - **返回:** `{ dislikesCount: number }` - `GET /api/lfrs/likes-users` — 获取 like 了某文档的用户 ID。 - **Query:** `collection` (必填), `id` (必填), `limit` (可选) - **返回:** `{ userIds: string[] }` - `GET /api/lfrs/dislikes-users` — 获取 dislike 了某文档的用户 ID。 - **Query:** `collection` (必填), `id` (必填), `limit` (可选) - **返回:** `{ userIds: string[] }` - **所有 `POST` 和 `DELETE` endpoints 都需要身份验证(带有用户上下文)。** ## 前端 UI 组件 该插件为您的前端应用程序提供了一套现成的 React 组件。这些组件通过 `payload-lfrs/client` 导出,并作为客户端组件(`"use client"`)构建,以无缝处理用户交互和乐观 UI 更新。 ### 可用组件 - **`LfrsLikeDislike`**:一个可切换的赞/踩小部件,显示当前计数。 - **`LfrsFavourite`**:一个用于保存文档的书签/收藏按钮。 - **`LfrsRating`**:一个交互式星级评分组件,供用户提交评分。 - **`LfrsRatingSummary`**:一个显示平均评分和分数分布的视觉摘要。 - **`LfrsComposeReview` / `LfrsComposeReply`**:用于提交文本评论和嵌套回复的表单。 - **`LfrsReviewCard` / `LfrsReplyCard`**:用于渲染单个评论和回复的展示组件。 - **`LfrsReviewsSection`**:一个完整、集成的评论区,结合了摘要、撰写表单和评论列表。 ### 示例用法 ``` import { LfrsLikeDislike, LfrsRating } from 'payload-lfrs/client' export function PostDetails({ post }) { return (

{post.title}

{/* Like / Dislike Toggle */} {/* 5-Star Rating */}
) } ``` ### UI 自定义 (CSS 变量) UI 组件旨在与您现有的网站无缝集成。它们使用 **CSS 变量**,您可以在应用中全局覆盖它们(例如在您的 `:root` 或 `body` 块中),或者使用 `style` prop 直接作用到组件上! 以下是可用的变量及其默认回退值: ``` :root { --lfrs-primary: #000000; /* Used for primary buttons (e.g. "Write a Review") */ --lfrs-text: #333333; /* Main text color */ --lfrs-text-muted: #666666; /* Dates, secondary text, placeholders */ --lfrs-bg: #ffffff; /* Main background color for cards */ --lfrs-bg-muted: #f5f5f5; /* Background for replies and forms */ --lfrs-border: #e0e0e0; /* Borders around cards and inputs */ --lfrs-star-active: #ffb400; /* Color of filled rating stars and bars */ --lfrs-star-inactive: #e0e0e0; /* Color of empty rating stars */ --lfrs-like-active: #0066cc; /* Active state for Like button */ --lfrs-dislike-active: #cc0000; /* Active state for Dislike button */ --lfrs-favourite-active: #ff0055; /* Active state for Favourite button */ --lfrs-radius: 6px; /* Border radius for buttons, inputs, and cards */ --lfrs-font: inherit; /* Font family inherited from your app by default */ } ``` #### 示例:内联作用域自定义 如果您想为单个组件设置样式,可以通过 `style` prop 传递变量: ``` ``` ## 构建自定义 UI(Headless 用法) 该插件被设计为完全与框架无关。虽然为了方便我们提供了 React 组件,但您可以通过直接与插件的 REST API 交互,在任何框架(Vue、Svelte、Angular、React Native 或原生 JavaScript)中构建您自己的自定义用户界面。 ### 自定义组件示例 以下是如何在原生 JavaScript 中构建自定义“Like”交互的示例: ``` async function toggleLike(targetCollection, targetDocId) { try { const response = await fetch('/api/lfrs/like', { method: 'POST', headers: { 'Content-Type': 'application/json', // 'Authorization': 'Bearer YOUR_TOKEN' // If required }, body: JSON.stringify({ collection: targetCollection, id: targetDocId, }), }) if (!response.ok) throw new Error('Failed to toggle like') const data = await response.json() // Update your custom UI state here... } catch (error) { console.error(error) } } ``` 同样,您可以使用 `GET /api/lfrs/status` endpoint 在页面加载时获取当前用户的交互状态,并将其他交互(Favorites、Ratings、Reviews)映射到它们各自的 endpoints。 ## 架构与开发者指南 如果您正在审查、贡献或调试此插件,以下是代码库结构和内部架构的概述。 ### 代码组织 - `src/plugin.ts`:主入口点。它接收用户配置,对其进行清理(应用默认值),并将集合、字段和 endpoints 注入到 Payload 配置中。 - `src/collections/`:包含插件管理的集合定义(`likes`、`dislikes`、`favourites`、`ratings`、`reviews`、`replies`)。它们存储了实际的用户交互。 - `src/fields/`: - `aggregateFields.ts`:生成注入到目标集合中的 `lfrs` 字段组(例如 `lfrs.likesCount`、`lfrs.averageRating`)。 - `joinFields.ts`:注入 Payload Join 字段,以便管理员可以直接从目标文档的 admin UI 中查看相关的 LFRs 文档。 - `src/endpoints/`:REST API 实现。它们处理传入的用户请求,执行访问控制,并执行数据库操作。 - `src/hooks/`:包含 Payload 的 lifecycle hooks。例如,`cascadeDelete.ts` 确保当目标文档被删除时,所有相关的交互也会被移除,以防止产生孤儿记录。 - `src/admin/`:用于 Payload Admin 面板的 React 组件。包括状态小部件和评论审核视图。 - `src/types.ts`:用于配置、内部清理配置和功能访问的 TypeScript 接口和类型。 ### 聚合计数逻辑 (Endpoint-Driven) 为了确保高可靠性并避免 Payload CMS 内出现事务上下文污染,聚合逻辑(例如更新文章的 `likesCount` 或 `dislikesCount`)主要采用 **Endpoint-Driven** 方式: 1. **Endpoints 抑制 Hooks**:当用户通过 API endpoints 进行交互时(例如 `/api/lfrs/like`),endpoints 会执行必要的数据库 mutations(`create`、`delete`),同时传递 `context: { skipLfrsHooks: true }`。这会抑制自动的基于 hook 的重新计算。 2. **显式更新**:所有 mutations 成功完成后,endpoint 会直接从数据库(作为真实数据源)显式统计交互,并对目标文档的聚合字段执行一次原子更新。 3. **Admin 面板回退**:`src/hooks/recalculateAggregates.ts` 中的 `afterChange` 和 `afterDelete` hooks 仍作为回退方案保留。如果管理员从 Payload Admin UI 手动创建或删除交互,它们将自动重新计算计数,从而保持数据的一致性。 ## License MIT
标签:CMS插件, Payload, Syscall, TypeScript, Web开发, 互动功能, 安全插件, 自动化攻击, 评论系统