@extends('admin.layouts.app') @section('title', '订阅管理') @section('page_title', '订阅管理') @section('content') @php // back 参数用于“返回上一页(保留上下文)”,但 back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) // 注意:使用相对路径而非绝对 URL,避免不同 APP_URL 环境影响,以及 show 页 back 安全校验(要求以 / 开头) $selfWithoutBack = \App\Support\BackUrl::selfWithoutBack(); $currentQuery = request()->query(); unset($currentQuery['back']); $back = $selfWithoutBack; // 用于构建“保留当前筛选上下文”的站内跳转链接:统一抽到 BackUrl,避免 Blade 内闭包口径漂移。 // 说明:这里不透传 back(避免嵌套/污染)。 $buildSelfUrl = function (array $overrides = []) use ($currentQuery) { return \App\Support\BackUrl::mergeQueryToCurrentPath($currentQuery, $overrides); }; // back 安全护栏(全页通用): // - 仅允许站内相对路径(/ 开头) // - 拒绝引号/尖括号(由于本页大量 href 采用 `{!! !!}` 原样输出,必须严控注入风险) // - 拒绝 nested back=(避免 URL 膨胀/绕过) $incomingBack = (string) request()->query('back', ''); $safeBackForLinks = \App\Support\BackUrl::sanitizeForLinks($incomingBack); // “从订单详情页来挑订阅”的治理交互: // - attach_order_id:表示把选中的订阅绑定回某个订单 // - attach_back:绑定成功后回跳到哪里(通常是订单详情页) $attachOrderId = (int) request()->query('attach_order_id', 0); $safeAttachBackForLinks = \App\Support\BackUrl::sanitizeForLinks((string) request()->query('attach_back', '')); // 用于摘要卡等入口:保留当前 query 并覆盖字段,同时安全透传 back。 $safeFullUrlWithQuery = function (array $overrides = []) use ($safeBackForLinks) { return \App\Support\BackUrl::currentPathWithQuery($overrides, $safeBackForLinks); }; $subscriptionIndexUrl = \App\Support\BackUrl::withBack('/admin/site-subscriptions', $safeBackForLinks); @endphp

快捷筛选

用于运营快速定位需要处理的订阅集合(口径基于筛选条件组合)。
@php // 快捷筛选:仅保留“上下文”字段(站点/套餐/keyword/安全 back),避免把其它筛选条件叠加导致空结果 $buildQuickFilterUrl = function (array $overrides) use ($safeBackForLinks) { return \App\Support\BackUrl::currentPathQuickFilter(['merchant_id', 'plan_id', 'keyword'], $overrides, $safeBackForLinks); }; // “全部”:清空筛选,但保留安全 back(用于返回来源页) $allUrl = \App\Support\BackUrl::withBack('/admin/site-subscriptions', $safeBackForLinks); @endphp
全部 已生效 待生效 已取消 已过期 7天内到期

到期治理

按到期时间(ends_at)快速定位需要续费/处理的订阅集合(不改变订阅 status 字段)。
@if(($filters['expiry'] ?? '') === 'expiring_7d')
7天内到期|站点维度提醒清单(Top10)
用于运营优先触达:按“到期订阅数”排序,同时展示最早到期时间(min_ends_at)。
@forelse(($expiryMerchantRows ?? []) as $row) @php $mid = (int) ($row['merchant_id'] ?? 0); $mname = (string) ($row['merchant_name'] ?? ''); $cnt = (int) ($row['count'] ?? 0); $minEndsAt = (string) ($row['min_ends_at'] ?? ''); $merchantUrl = $buildSelfUrl(['merchant_id' => $mid, 'page' => null]); @endphp @empty @endforelse
站点 到期订阅数 最早到期
{{ $mname !== '' ? $mname : ('站点#' . $mid) }} @php $renewalCtaUrlByMerchant = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([ 'order_type' => 'renewal', 'require_subscription' => '1', 'merchant_id' => $mid, ]), $selfWithoutBack); @endphp
去创建续费订单 (建议:进入后按订阅维度续费)
{{ $cnt }} @php $barPct = 0; if ((int) ($summaryStats['expiring_7d_subscriptions'] ?? 0) > 0) { $barPct = min(100, max(0, round(((int) $cnt / (int) ($summaryStats['expiring_7d_subscriptions'] ?? 1)) * 100, 1))); } @endphp
{{ $minEndsAt !== '' ? $minEndsAt : '-' }}
暂无数据
7天内到期|站点 + 套餐维度提醒清单(Top10)
用于更精确续费下单:按到期订阅数排序,展示最早到期时间;并提供 merchant+plan 的续费下单入口。
@forelse(($expiryMerchantPlanRows ?? []) as $row) @php $mid = (int) ($row['merchant_id'] ?? 0); $mname = (string) ($row['merchant_name'] ?? ''); $pid = (int) ($row['plan_id'] ?? 0); $pname = (string) ($row['plan_name'] ?? ''); $cnt = (int) ($row['count'] ?? 0); $minEndsAt = (string) ($row['min_ends_at'] ?? ''); $mpUrl = $buildSelfUrl(['merchant_id' => $mid, 'plan_id' => $pid, 'page' => null]); $renewalCtaUrlByMerchantPlan = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([ 'order_type' => 'renewal', 'require_subscription' => '1', 'merchant_id' => $mid, 'plan_id' => $pid, ]), $selfWithoutBack); @endphp @empty @endforelse
站点 套餐 到期订阅数 最早到期
{{ $mname !== '' ? $mname : ('站点#' . $mid) }} {{ $pname !== '' ? $pname : ('套餐#' . $pid) }} {{ $cnt }} @php $barPct = 0; if ((int) ($summaryStats['expiring_7d_subscriptions'] ?? 0) > 0) { $barPct = min(100, max(0, round(((int) $cnt / (int) ($summaryStats['expiring_7d_subscriptions'] ?? 1)) * 100, 1))); } @endphp
{{ $minEndsAt !== '' ? $minEndsAt : '-' }}
去创建续费订单 (merchant+plan 预填)
暂无数据
@endif @php $expiredUrl = $buildQuickFilterUrl(['status' => null, 'expiry' => 'expired']); $expiring7dUrl = $buildQuickFilterUrl(['status' => null, 'expiry' => 'expiring_7d']); @endphp
已过期({{ $summaryStats['expired_subscriptions'] ?? 0 }}) 7天内到期({{ $summaryStats['expiring_7d_subscriptions'] ?? 0 }}) @php // 当已处于“到期集合”视图时,补一个就近的续费下单入口(带回退到当前列表的 back)。 $isExpiryView = in_array((string) ($filters['expiry'] ?? ''), ['expired', 'expiring_7d'], true); $renewalCtaUrl = ''; if ($isExpiryView) { $q = [ 'order_type' => 'renewal', // 续费单必须绑定订阅:集合入口也应尽可能引导到订阅维度续费(批量另行建设)。 'require_subscription' => '1', ]; if ((int) ($filters['merchant_id'] ?? 0) > 0) { $q['merchant_id'] = (int) $filters['merchant_id']; } if ((int) ($filters['plan_id'] ?? 0) > 0) { $q['plan_id'] = (int) $filters['plan_id']; } $renewalCtaUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query($q), $selfWithoutBack); } @endphp @if($isExpiryView) 创建续费订单(当前集合) (建议:进入后按订阅维度续费) @endif
建议:先处理“7天内到期”续费触达,再处理“已过期”补单或关闭。

筛选条件

点击收起/展开
@if($safeBackForLinks !== '') @endif
@php $endsToday = now()->format('Y-m-d'); $ends7d = now()->addDays(7)->format('Y-m-d'); $ends30d = now()->addDays(30)->format('Y-m-d'); // 到期区间快捷入口:应保留当前筛选上下文(status/expiry/merchant/plan/keyword/back),仅覆盖 ends_from/ends_to,并清空 page。 $endsQuickTodayUrl = $safeFullUrlWithQuery(['ends_from' => $endsToday, 'ends_to' => $endsToday, 'page' => null]); $endsQuick7dUrl = $safeFullUrlWithQuery(['ends_from' => $endsToday, 'ends_to' => $ends7d, 'page' => null]); $endsQuick30dUrl = $safeFullUrlWithQuery(['ends_from' => $endsToday, 'ends_to' => $ends30d, 'page' => null]); $endsQuickClearUrl = $safeFullUrlWithQuery(['ends_from' => null, 'ends_to' => null, 'page' => null]); @endphp 今天到期 近7天到期 近30天到期 清空到期区间

订阅总数

已生效

待生效

已取消

已过期(按到期时间)

7天内到期

工具

@php $batchMarkExpiredEnabled = (string) ($filters['expiry'] ?? '') === 'expired'; $batchMarkExpiredReason = $batchMarkExpiredEnabled ? '' : '请先进入「已过期(expiry=expired)」集合后再执行批量标记。'; @endphp
@php $q = [ 'order_type' => 'renewal', 'require_subscription' => '1', ]; if ((int) ($filters['merchant_id'] ?? 0) > 0) { $q['merchant_id'] = (int) $filters['merchant_id']; } if ((int) ($filters['plan_id'] ?? 0) > 0) { $q['plan_id'] = (int) $filters['plan_id']; } $createOrderFromSubIndexUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query($q), $selfWithoutBack); @endphp @if(!($isExpiryView ?? false) && $attachOrderId <= 0) 续费下单(先选订阅) @endif @if($attachOrderId <= 0)
@csrf
@if(! $batchMarkExpiredEnabled)
提示:{{ $batchMarkExpiredReason }}
@php // 提效:被阻断时给一键跳转到「已过期(expiry=expired)」集合,避免运营来回找入口。 // 说明:这里选择保留当前 ends_from/ends_to 区间(若有),避免运营还要重新输入到期范围。 $goExpiredUrl = $buildSelfUrl(['status' => null, 'expiry' => 'expired', 'page' => null]); @endphp
@endif
@endif
@if($attachOrderId > 0) 当前为“绑定订阅到订单”模式:请在列表行内直接点击「绑定到订单」。 @else 用于运营从订阅目录快速发起续费下单:会把当前 merchant_id/plan_id 作为默认值带到下单页。 续费单必须绑定订阅,建议从下方列表行内「续费下单」选择具体订阅。 @endif @if(($isExpiryView ?? false)) (已处于到期集合视图:请优先使用上方「创建续费订单(当前集合)」入口) @endif

订阅列表

@forelse($subscriptions as $subscription) @empty @endforelse
ID 订阅号 站点 套餐 状态 计费周期 周期(月) 金额 开始时间 到期时间 到期状态 关联订单数 生效时间
{{ $subscription->id }} @php $subShowUrl = \App\Support\BackUrl::withBack('/admin/site-subscriptions/' . $subscription->id, $back); @endphp {{ $subscription->subscription_no }} @php $remarkPrefix = (string) config('saasshop.platform_orders.renewal_order_remark_prefix', '来自订阅:'); $q = [ 'order_type' => 'renewal', 'require_subscription' => '1', 'site_subscription_id' => $subscription->id, 'quantity' => 1, 'remark' => $remarkPrefix . $subscription->subscription_no, ]; if ((int) ($subscription->merchant_id ?? 0) > 0) { $q['merchant_id'] = (int) $subscription->merchant_id; } if ((int) ($subscription->plan_id ?? 0) > 0) { $q['plan_id'] = (int) $subscription->plan_id; } $renewOrderUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query($q), $back); @endphp
续费下单 @if($attachOrderId > 0) @php // 从订单详情进入订阅管理页时:提供“绑定到该订单”的治理按钮 // 注意:提交后由 attachSubscription 做强校验(续费单 + merchant/plan 一致) $attachBack = $safeAttachBackForLinks !== '' ? $safeAttachBackForLinks : $safeBackForLinks; if ($attachBack === '') { $attachBack = $back; } @endphp
@csrf @if($attachBack !== '') @endif
@endif
@csrf
@if($subscription->merchant) {{ $subscription->merchant->name }} @else 未关联站点 @endif @if($subscription->plan) {{ $subscription->plan_name ?: $subscription->plan->name }} @else {{ $subscription->plan_name ?: '未设置' }} @endif {{ ($statusLabels[$subscription->status] ?? $subscription->status) }} ({{ $subscription->status }}) {{ $subscription->billing_cycle ?: '-' }} {{ $subscription->period_months }} ¥{{ number_format((float) $subscription->amount, 2) }} {{ optional($subscription->starts_at)->format('Y-m-d H:i:s') ?: '-' }} {{ optional($subscription->ends_at)->format('Y-m-d H:i:s') ?: '-' }} @php $endsAt = $subscription->ends_at; $expiryLabel = '无到期'; if ($endsAt) { if ($endsAt->lt(now())) { $expiryLabel = '已过期'; } elseif ($endsAt->lt(now()->addDays(7))) { $expiryLabel = '7天内到期'; } else { $expiryLabel = '未到期'; } } @endphp {{ $expiryLabel }} @php $cnt = (int) ($subscription->platform_orders_count ?? 0); @endphp @if($cnt > 0) @php // 跳到平台订单页:附带 back 回到“订阅列表页自身(剔除 back)”,并统一走 BackUrl helper。 $platformOrdersUrl = \App\Support\BackUrl::withBack( '/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'site_subscription_id' => $subscription->id, ]), $back ); @endphp {{ $cnt }} @else 0 @endif {{ optional($subscription->activated_at)->format('Y-m-d H:i:s') ?: '-' }}
暂无订阅数据,当前阶段先把订阅主表与总台目录立起来,后续再接订阅创建/激活/续费链路。
{{ $subscriptions->links('pagination.admin') }}
@endsection