- 文章列表:会员插件注入「付费」徽章列(会员/付费¥xx,停插件即消失) - 分类:真实文章数(withCount)、名称点击跳文章列表并按分类筛选(ListPosts booted 预置筛选) - 分类:新增封面(medialibrary)+ 介绍(description 字段/表单);前台分类页顶部展示介绍区;SEO description + BreadcrumbList 结构化数据(GEO) - 文章编辑:MarkdownEditor 内容区加大(26rem);文章编辑页新增「评论」关系管理页(可审/拒) - 评论列表:文章列带摘要 - 三套主题补 .category-intro 样式 - 测试更新(付费徽章列)
136 lines
5.4 KiB
PHP
136 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
namespace Plugins\Neatstudio\Membership;
|
|
|
|
use App\Blog\Support\PluginManager;
|
|
use App\Blog\Support\PluginServiceProvider;
|
|
use App\Models\Post;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Table;
|
|
use Plugins\Neatstudio\Membership\Models\MembershipPlan;
|
|
use Plugins\Neatstudio\Membership\Models\Subscription;
|
|
use Plugins\Neatstudio\Payment\Models\Payment;
|
|
|
|
class ServiceProvider extends PluginServiceProvider
|
|
{
|
|
protected function boot(PluginManager $manager): void
|
|
{
|
|
$this->loadRoutes(__DIR__.'/../routes/web.php');
|
|
|
|
// 支付成功:按购买实体分发(激活会员订阅 / 解锁单篇付费文章)
|
|
$manager->addAction('payment.paid', function (Payment $payment) {
|
|
$user = $payment->user;
|
|
$payable = $payment->payable;
|
|
|
|
if (! $user || ! $payable) {
|
|
return;
|
|
}
|
|
|
|
if ($payable instanceof MembershipPlan) {
|
|
$now = now();
|
|
Subscription::create([
|
|
'user_id' => $user->id,
|
|
'membership_plan_id' => $payable->id,
|
|
'status' => 'active',
|
|
'starts_at' => $now,
|
|
'ends_at' => $now->copy()->addDays($payable->duration_days),
|
|
]);
|
|
}
|
|
|
|
if ($payable instanceof Post) {
|
|
$meta = $payable->meta ?? [];
|
|
$unlocked = $meta['unlocked_user_ids'] ?? [];
|
|
$unlocked[] = $user->id;
|
|
$payable->update(['meta' => array_merge($meta, ['unlocked_user_ids' => array_values(array_unique($unlocked))])]);
|
|
}
|
|
}, 10);
|
|
|
|
// 付费文章:[paid]...[/paid] —— 无权限访客替换为 teaser;有权限替换为付费内容块(避免原始标签裸露在页面)
|
|
$manager->addFilter('post.rendered', function (string $html, \App\Models\Post $post) {
|
|
$service = app(Services\MembershipService::class);
|
|
$user = auth()->user();
|
|
|
|
if (preg_match('/\[paid\](.*?)\[\/paid\]/s', $html)) {
|
|
// Markdown 转换会给短代码包上 <p>,先去掉,避免替换后残留未闭合标签
|
|
$html = preg_replace('/<p>\s*\[(\/?paid)\]\s*<\/p>/i', '[$1]', $html);
|
|
|
|
$html = preg_replace_callback(
|
|
'/\[paid\](.*?)\[\/paid\]/s',
|
|
function (array $m) use ($service, $user, $post) {
|
|
if ($service->canReadPaidContent($user, $post)) {
|
|
return '<div class="paid-content"><div class="paid-content-label">付费内容</div>'.trim($m[1]).'</div>';
|
|
}
|
|
|
|
return view('membership.paid-teaser', ['post' => $post])->render();
|
|
},
|
|
$html
|
|
);
|
|
}
|
|
|
|
if (! $service->canReadPost($user, $post)) {
|
|
return view('membership.paywall', ['post' => $post])->render();
|
|
}
|
|
|
|
return $html;
|
|
}, 10);
|
|
|
|
// 后台文章表单注入:付费设置(meta.price 单篇价格 + meta.members_only 会员专享)
|
|
$manager->addAction('filament.post_form', function (Schema $schema) {
|
|
$schema->components([
|
|
...$schema->getComponents(),
|
|
Section::make('付费设置')
|
|
->description('需启用支付插件并配置支付渠道;0 = 免费')
|
|
->columns(3)
|
|
->collapsible()
|
|
->schema([
|
|
TextInput::make('meta.price')
|
|
->label('单篇价格(分)')
|
|
->numeric()
|
|
->minValue(0)
|
|
->default(0)
|
|
->helperText('例:9900 = ¥99;填 0 表示单篇免费'),
|
|
Toggle::make('meta.members_only')
|
|
->label('会员专享')
|
|
->helperText('整篇文章仅会员可读'),
|
|
]),
|
|
]);
|
|
});
|
|
|
|
// 后台文章表格注入:付费标识(价格/会员徽章,停用插件后消失)
|
|
$manager->addAction('filament.post_table', function (Table $table) {
|
|
$table->pushColumns([
|
|
TextColumn::make('meta.members_only')
|
|
->label('付费')
|
|
->badge()
|
|
->formatStateUsing(function ($state, $record) {
|
|
$meta = $record->meta ?? [];
|
|
$price = (int) ($meta['price'] ?? 0);
|
|
|
|
if (! empty($meta['members_only'])) {
|
|
return '会员';
|
|
}
|
|
|
|
return $price > 0 ? '付费 ¥'.number_format($price / 100, 2) : '—';
|
|
})
|
|
->color(function ($state, $record) {
|
|
$meta = $record->meta ?? [];
|
|
|
|
if (! empty($meta['members_only'])) {
|
|
return 'warning';
|
|
}
|
|
|
|
return (int) ($meta['price'] ?? 0) > 0 ? 'danger' : 'gray';
|
|
})
|
|
->placeholder('—'),
|
|
]);
|
|
});
|
|
}
|
|
}
|