From 4d63c0c68361a865add3ec7e44c0415b42c6257b Mon Sep 17 00:00:00 2001 From: ak Date: Wed, 12 Aug 2026 01:23:37 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=8F=92=E4=BB=B6/=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=96=87=E6=A1=A3=E8=A1=A5=E5=85=A8=E6=96=B0?= =?UTF-8?q?=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - plugins.md:requires 依赖强制校验行为、filament.post_form/post_table 后台注入(追加写法 + 示例)、Payment payable 商品化约定(payableLabel/payableUrl)、paywall 视图覆盖机制 - themes.md:新增付费墙覆盖章节(membership.paywall / paid-teaser,主题同名文件可覆盖) - README:插件段落补充依赖校验、后台注入钩子、订单商品化说明 --- README.md | 5 +++- docs/plugins.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++-- docs/themes.md | 10 ++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7aef474..c599663 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,10 @@ S3_ACCESS_KEY=... S3_BUCKET=... S3_ENDPOINT=... php artisan attachments:sync-s3 - 目录:`plugins/{vendor}.{name}/`,含 `plugin.json` + `src/ServiceProvider.php`(继承 `App\Blog\Support\PluginServiceProvider`)+ 可选 `database/migrations`、`routes/web.php`、`views/` - 钩子:`addAction(hook, cb)` / `addFilter(hook, cb, priority)`,核心钩子: - - `comment.created`(AI 审核)、`payment.paid`(订阅激活/解锁)、`post.rendered`(付费内容过滤)、`seo.structured_data` + - `comment.created`(AI 审核)、`payment.paid`(按 payable 分发订阅激活/解锁)、`post.rendered`(付费内容过滤/paywall)、`seo.structured_data` + - `filament.post_form` / `filament.post_table`(后台文章表单/表格注入) +- 插件依赖 `requires` 强制校验:启用需依赖已启用,停用/卸载会被依赖者阻止;启动时依赖不满足的插件跳过 +- 订单商品化:`Payment` 多态 `payable` 关联购买实体,实体实现 `payableLabel()` / `payableUrl()` 接入订单展示(详见 docs/plugins.md) - 后台「插件管理」:启停 / 上传 ZIP 安装 / 卸载 / 市场拉取(配置 `MARKET_URL` 后可用) - 插件自带迁移由 `migrate` 自动加载(`app/Providers/AppServiceProvider` 注册 plugins/*/database/migrations) diff --git a/docs/plugins.md b/docs/plugins.md index d44b206..b972734 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -36,6 +36,15 @@ plugins/{vendor}.{name}/ | `requires` | 依赖的其他插件(如会员依赖支付),按 `vendor.name` 引用 | | `filament_pages` / `filament_resources` | 注册到后台的页面/资源(由 `PluginPages` 汇总,统一归入「插件」导航分组) | +## 依赖管理(requires 强制校验) + +- `enable()`:依赖未安装或未启用 → 拒绝启用并提示(后台弹错误通知) +- `disable()` / `uninstall()`:有已启用的插件依赖它 → 拒绝操作,需先停用上层插件 +- 应用启动时依赖未满足的插件**跳过 boot**,避免运行时缺底层能力 +- 后台插件卡片展示依赖链状态(徽章:✓ 已启用 / ✗ 未安装 / ✗ 未启用),依赖不满足的插件无法启用 + +插件分层示例:`payment`(支付)→ `membership`(会员,requires payment)→ 你的业务插件(requires membership 或 payment)。上层插件可放心假设底层能力存在。 + ## ServiceProvider ```php @@ -74,16 +83,71 @@ class ServiceProvider extends PluginServiceProvider | Hook | 参数 | 用途 | |------|------|------| | `comment.created` | `Comment` | 新评论创建(AI 审核在此监听) | -| `payment.paid` | `Payment` | 支付成功(订阅激活/解锁在此监听) | +| `payment.paid` | `Payment` | 支付成功(按 `$payment->payable` 分发:订阅激活/解锁) | +| `filament.post_form` | `Schema` | 文章编辑表单追加字段(会员付费设置在此注入) | +| `filament.post_table` | `Table` | 文章列表追加列/操作/过滤 | ### addFilter(hook, callback, priority) — 返回值传给下一个过滤器 | Hook | 签名 | 用途 | |------|------|------| -| `post.rendered` | `(string $html, Post $post): string` | 文章渲染后处理(付费内容过滤) | +| `post.rendered` | `(string $html, Post $post): string` | 文章渲染后处理(付费内容过滤、paywall) | | `seo.structured_data` | `(array $data): array` | 扩展 JSON-LD 结构化数据 | | `payment.gateway` | `(array $gateways): array` | 注册支付渠道 | +## 后台注入(Filament 表单 / 表格) + +核心资源的表单/表格会触发 `filament.post_form` / `filament.post_table` 两个钩子,插件可追加自己的字段与列,停用插件后字段自动消失,核心零侵入。 + +> 注意:`Schema::components()` 与 `Table::columns()` 都是**整体替换**语义,追加要这样写: +> - 表单:`$schema->components([...$schema->getComponents(), Section::make(...)])` +> - 表格:`$table->pushColumns([...])` + +示例(给文章加「付费设置」): + +```php +$manager->addAction('filament.post_form', function (Schema $schema) { + $schema->components([ + ...$schema->getComponents(), + Section::make('付费设置')->columns(3)->collapsible()->schema([ + TextInput::make('meta.price')->label('单篇价格(分)')->numeric()->default(0), + Toggle::make('meta.members_only')->label('会员专享'), + ]), + ]); +}); + +$manager->addAction('filament.post_table', function (Table $table) { + $table->pushColumns([ + TextColumn::make('meta.price')->label('单篇价格'), + IconColumn::make('meta.members_only')->label('会员')->boolean(), + ]); +}); +``` + +字段用点号路径(`meta.price`)直接映射到 `Post` 的 `meta` JSON。核心保存时会把表单里的 meta 键与已有 meta **合并**,不会覆盖其他插件写入的键(如解锁用户)。 + +## 订单商品化(可购买实体) + +`Payment` 用多态关联指向被购买的实体(`payable_type` / `payable_id`)。支付成功后的 `payment.paid` 钩子按 `$payment->payable` 分发(`instanceof` 判断),**不要**再用字符串解析业务类型。 + +订单后台的「商品」列与跳转链接由实体上的两个约定方法生成: + +```php +public function payableLabel(): string { return '文章:'.$this->title; } +public function payableUrl(): ?string { return route('filament.admin.resources.posts.edit', $this); } +``` + +已实现:`App\Models\Post`(单篇解锁)、`MembershipPlan`(会员套餐)。新实体(皮肤授权、插件授权、打赏)实现同样两个方法即自动接入订单展示,支付插件零改动。 + +## 付费内容前台展示(paywall) + +会员插件在 `post.rendered` 过滤器里处理两种付费形态: + +- 整篇锁定(`meta.members_only`)→ 渲染 `membership.paywall`(封面 + 标题 + 价格 + 单篇解锁/开通会员按钮) +- `[paid]` 付费块 → 替换为 `membership.paid-teaser`(内嵌解锁按钮的提示块) + +缺省视图在 `resources/views/membership/`,主题可覆盖:`themes/{name}/views/membership/paywall.blade.php`。视图内可用 `$post`,按钮沿用 `.btn` 类,`.paywall` / `.paid-teaser` 样式两套内置主题已带。 + ## 迁移 插件迁移放在 `database/migrations/`,`app/Providers/AppServiceProvider` 启动时自动注册,`php artisan migrate` 会一并执行(无需手动 --path)。 diff --git a/docs/themes.md b/docs/themes.md index 8b794cc..d05adda 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -82,8 +82,18 @@ themes/{name}/ | `comments` | CommentController | `$comments` | | `login` / `register` / `profile` | AuthController | — | | `membership.index` / `membership.mine` | 会员插件 | `$plans / $subscriptions` | +| `membership.paywall` / `membership.paid-teaser` | 会员插件(付费墙) | `$post` | | `payments.sandbox` / `payments.result` | 支付插件 | `$payment` | +## 付费墙(paywall)覆盖 + +启用会员插件后,付费文章的前台展示走两个视图(缺省在 `resources/views/membership/`): + +- `membership.paywall`:整篇锁定(会员专享)时替代正文,含封面/标题/价格/解锁按钮 +- `membership.paid-teaser`:正文内 `[paid]` 付费块的替换提示 + +主题可放同名文件自定义样式与文案,如 `themes/{name}/views/membership/paywall.blade.php`。视图内可用 `$post`,按钮建议沿用 `.btn` 类;`.paywall` / `.paid-teaser` 两套内置主题已带样式。 + ## 侧边栏共享数据(SidebarComposer 自动注入所有前台视图) `$categories`、`$recentPosts`、`$recentComments`、`$hotTags`、`$links`、`$blogStats`、`$siteName`、`$siteDescription`、`$siteIcp`