feat(dashboard): 平台定位改为运营版北极星指标+治理Top3
This commit is contained in:
@@ -159,10 +159,13 @@ class DashboardController extends Controller
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 统一基准时间:避免本方法内多次 now() 调用在跨天瞬间造成口径漂移
|
||||||
|
$baseNow = now();
|
||||||
|
|
||||||
// 趋势卡(最小可用):近 7 天平台订单按天统计(订单数 + 已付金额)
|
// 趋势卡(最小可用):近 7 天平台订单按天统计(订单数 + 已付金额)
|
||||||
$trendDays = 7;
|
$trendDays = 7;
|
||||||
$trendStart = now()->startOfDay()->subDays($trendDays - 1);
|
$trendStart = $baseNow->copy()->startOfDay()->subDays($trendDays - 1);
|
||||||
$trendEnd = now()->endOfDay();
|
$trendEnd = $baseNow->copy()->endOfDay();
|
||||||
|
|
||||||
// 统一提供给视图做“趋势/排行/占比”跳转的日期范围口径,避免 Blade 内重复 now() 计算导致漂移。
|
// 统一提供给视图做“趋势/排行/占比”跳转的日期范围口径,避免 Blade 内重复 now() 计算导致漂移。
|
||||||
$dashboardRangeFrom7d = (string) $trendStart->format('Y-m-d');
|
$dashboardRangeFrom7d = (string) $trendStart->format('Y-m-d');
|
||||||
@@ -264,6 +267,86 @@ class DashboardController extends Controller
|
|||||||
|
|
||||||
$merchantIdToName = Merchant::query()->pluck('name', 'id')->all();
|
$merchantIdToName = Merchant::query()->pluck('name', 'id')->all();
|
||||||
|
|
||||||
|
// 平台定位(运营版):北极星指标 + 明确治理入口(不暴露组合维度)
|
||||||
|
$range30Start = $baseNow->copy()->subDays(29)->startOfDay();
|
||||||
|
$range30End = $baseNow->copy()->endOfDay();
|
||||||
|
$range30From = (string) $range30Start->format('Y-m-d');
|
||||||
|
$range30To = (string) $range30End->format('Y-m-d');
|
||||||
|
|
||||||
|
$paidRevenue30d = (float) PlatformOrder::query()
|
||||||
|
->where('payment_status', 'paid')
|
||||||
|
->whereBetween('created_at', [$range30Start, $range30End])
|
||||||
|
->sum('paid_amount');
|
||||||
|
|
||||||
|
$activePaidMerchants = (int) SiteSubscription::query()
|
||||||
|
->whereNotNull('merchant_id')
|
||||||
|
->where('status', 'activated')
|
||||||
|
->whereNotNull('ends_at')
|
||||||
|
->where('ends_at', '>=', $baseNow)
|
||||||
|
->distinct()
|
||||||
|
->count('merchant_id');
|
||||||
|
|
||||||
|
$renewalCreated30d = (int) PlatformOrder::query()
|
||||||
|
->where('order_type', 'renewal')
|
||||||
|
->whereBetween('created_at', [$range30Start, $range30End])
|
||||||
|
->count();
|
||||||
|
$renewalSuccess30d = (int) PlatformOrder::query()
|
||||||
|
->where('order_type', 'renewal')
|
||||||
|
->where('payment_status', 'paid')
|
||||||
|
->where('status', 'activated')
|
||||||
|
->whereBetween('created_at', [$range30Start, $range30End])
|
||||||
|
->count();
|
||||||
|
$renewalSuccessRate30d = $renewalCreated30d > 0
|
||||||
|
? min(100, max(0, round(($renewalSuccess30d / $renewalCreated30d) * 100, 1)))
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
$funnelUnpaidPending7d = (int) PlatformOrder::query()
|
||||||
|
->whereBetween('created_at', [$trendStart, $trendEnd])
|
||||||
|
->where('payment_status', 'unpaid')
|
||||||
|
->where('status', 'pending')
|
||||||
|
->count();
|
||||||
|
$funnelPaid7d = (int) PlatformOrder::query()
|
||||||
|
->whereBetween('created_at', [$trendStart, $trendEnd])
|
||||||
|
->where('payment_status', 'paid')
|
||||||
|
->count();
|
||||||
|
$funnelPaidActivated7d = (int) PlatformOrder::query()
|
||||||
|
->whereBetween('created_at', [$trendStart, $trendEnd])
|
||||||
|
->where('payment_status', 'paid')
|
||||||
|
->where('status', 'activated')
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 将平台定位的关键指标与“可执行动作入口”绑定(回到仪表盘自身)
|
||||||
|
$opsLinks = [
|
||||||
|
'revenue_30d_paid_orders' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'created_from' => $range30From,
|
||||||
|
'created_to' => $range30To,
|
||||||
|
]), \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
'active_paid_merchants_subscriptions' => \App\Support\BackUrl::withBack('/admin/site-subscriptions?status=activated', \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
'renewal_orders_30d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||||||
|
'order_type' => 'renewal',
|
||||||
|
'created_from' => $range30From,
|
||||||
|
'created_to' => $range30To,
|
||||||
|
]), \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
'funnel_unpaid_pending_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||||||
|
'payment_status' => 'unpaid',
|
||||||
|
'status' => 'pending',
|
||||||
|
'created_from' => $dashboardRangeFrom7d,
|
||||||
|
'created_to' => $dashboardRangeTo7d,
|
||||||
|
]), \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
'funnel_paid_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'created_from' => $dashboardRangeFrom7d,
|
||||||
|
'created_to' => $dashboardRangeTo7d,
|
||||||
|
]), \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
'funnel_paid_activated_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'status' => 'activated',
|
||||||
|
'created_from' => $dashboardRangeFrom7d,
|
||||||
|
'created_to' => $dashboardRangeTo7d,
|
||||||
|
]), \App\Support\BackUrl::selfWithoutBack()),
|
||||||
|
];
|
||||||
|
|
||||||
return view('admin.dashboard', [
|
return view('admin.dashboard', [
|
||||||
'adminName' => $admin->name,
|
'adminName' => $admin->name,
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
@@ -283,6 +366,27 @@ class DashboardController extends Controller
|
|||||||
'store' => config('cache.default'),
|
'store' => config('cache.default'),
|
||||||
'ttl' => '10m',
|
'ttl' => '10m',
|
||||||
],
|
],
|
||||||
|
'platformOpsOverview' => [
|
||||||
|
// 北极星指标
|
||||||
|
'paid_revenue_30d' => $paidRevenue30d,
|
||||||
|
'active_paid_merchants' => $activePaidMerchants,
|
||||||
|
'renewal_success_rate_30d' => $renewalSuccessRate30d,
|
||||||
|
'renewal_success_30d' => $renewalSuccess30d,
|
||||||
|
'renewal_created_30d' => $renewalCreated30d,
|
||||||
|
|
||||||
|
// 漏斗(近7天)
|
||||||
|
'funnel_unpaid_pending_7d' => $funnelUnpaidPending7d,
|
||||||
|
'funnel_paid_7d' => $funnelPaid7d,
|
||||||
|
'funnel_paid_activated_7d' => $funnelPaidActivated7d,
|
||||||
|
|
||||||
|
// 待处理治理(积压口径,全量)
|
||||||
|
'govern_bmpa_processable' => (int) ($stats['platform_orders_unpaid_pending'] ?? 0),
|
||||||
|
'govern_syncable' => (int) ($stats['platform_orders_syncable'] ?? 0),
|
||||||
|
'govern_sync_failed' => (int) ($stats['platform_orders_sync_failed'] ?? 0),
|
||||||
|
|
||||||
|
'links' => $opsLinks,
|
||||||
|
],
|
||||||
|
|
||||||
'platformOverview' => [
|
'platformOverview' => [
|
||||||
'system_role' => '总台管理',
|
'system_role' => '总台管理',
|
||||||
'current_scope' => '总台运营方视角',
|
'current_scope' => '总台运营方视角',
|
||||||
|
|||||||
@@ -462,16 +462,75 @@
|
|||||||
<div class="muted muted-xs mt-10">说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。</div>
|
<div class="muted muted-xs mt-10">说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" data-role="dashboard-platform-ops-overview">
|
||||||
<h3 class="mt-0">平台定位</h3>
|
<h3 class="mt-0">平台定位(运营版)</h3>
|
||||||
<table>
|
<div class="muted">只保留“看完知道下一步做什么”的北极星指标与治理积压。</div>
|
||||||
<tr><th>后台角色</th><td>{{ $platformOverview['system_role'] }}</td></tr>
|
|
||||||
<tr><th>当前视角</th><td>{{ $platformOverview['current_scope'] }}</td></tr>
|
@php
|
||||||
<tr><th>商家模式</th><td>{{ $platformOverview['merchant_mode'] }}</td></tr>
|
$ops = $platformOpsOverview ?? [];
|
||||||
<tr><th>渠道数</th><td>{{ $platformOverview['channel_count'] }}</td></tr>
|
$opsLinks = (array) ($ops['links'] ?? []);
|
||||||
<tr><th>活跃商家</th><td>{{ $platformOverview['active_merchants'] }}</td></tr>
|
$paidRevenue30d = (float) ($ops['paid_revenue_30d'] ?? 0);
|
||||||
<tr><th>待处理订单</th><td>{{ $platformOverview['pending_orders'] }}</td></tr>
|
$activePaidMerchants = (int) ($ops['active_paid_merchants'] ?? 0);
|
||||||
|
$renewalRate30d = (float) ($ops['renewal_success_rate_30d'] ?? 0);
|
||||||
|
$renewalSuccess30d = (int) ($ops['renewal_success_30d'] ?? 0);
|
||||||
|
$renewalCreated30d = (int) ($ops['renewal_created_30d'] ?? 0);
|
||||||
|
|
||||||
|
$funnelUnpaidPending7d = (int) ($ops['funnel_unpaid_pending_7d'] ?? 0);
|
||||||
|
$funnelPaid7d = (int) ($ops['funnel_paid_7d'] ?? 0);
|
||||||
|
$funnelPaidActivated7d = (int) ($ops['funnel_paid_activated_7d'] ?? 0);
|
||||||
|
|
||||||
|
$goBmpa = (int) ($ops['govern_bmpa_processable'] ?? 0);
|
||||||
|
$goSyncable = (int) ($ops['govern_syncable'] ?? 0);
|
||||||
|
$goSyncFailed = (int) ($ops['govern_sync_failed'] ?? 0);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<table class="mt-10">
|
||||||
|
<tr>
|
||||||
|
<th>近30天已收款</th>
|
||||||
|
<td>
|
||||||
|
<a class="link" href="{!! (string) ($opsLinks['revenue_30d_paid_orders'] ?? $billingEntryLinks['platform_orders']) !!}">
|
||||||
|
¥{{ number_format($paidRevenue30d, 2) }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>活跃付费站点</th>
|
||||||
|
<td>
|
||||||
|
<a class="link" href="{!! (string) ($opsLinks['active_paid_merchants_subscriptions'] ?? $billingEntryLinks['site_subscriptions']) !!}">
|
||||||
|
{{ $activePaidMerchants }}
|
||||||
|
</a>
|
||||||
|
<span class="muted muted-xs">(以“已生效且未到期订阅”估算)</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>续费成功率(30天)</th>
|
||||||
|
<td>
|
||||||
|
<a class="link" href="{!! (string) ($opsLinks['renewal_orders_30d'] ?? $billingEntryLinks['platform_orders']) !!}">
|
||||||
|
{{ $renewalRate30d }}%
|
||||||
|
</a>
|
||||||
|
<span class="muted muted-xs">({{ $renewalSuccess30d }} / {{ $renewalCreated30d }})</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
<div class="muted"><strong>收款漏斗(近7天)</strong></div>
|
||||||
|
<div class="muted muted-xs mt-6">用于快速判断卡点:催付 / 治理生效 / 同步订阅。</div>
|
||||||
|
<div class="actions gap-10 mt-6">
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! (string) ($opsLinks['funnel_unpaid_pending_7d'] ?? $platformOrdersQuickLinks['unpaid_pending']) !!}">待支付 {{ $funnelUnpaidPending7d }}</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! (string) ($opsLinks['funnel_paid_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}">已支付 {{ $funnelPaid7d }}</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! (string) ($opsLinks['funnel_paid_activated_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}">已生效 {{ $funnelPaidActivated7d }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
<div class="muted"><strong>待处理治理(Top3)</strong></div>
|
||||||
|
<div class="actions gap-10 mt-6">
|
||||||
|
<a class="btn btn-sm" href="{!! $platformOrdersQuickLinks['paid_pending'] !!}">可BMPA {{ $goBmpa }}</a>
|
||||||
|
<a class="btn btn-sm" href="{!! $platformOrdersQuickLinks['syncable_only'] !!}">可同步 {{ $goSyncable }}</a>
|
||||||
|
<a class="btn btn-sm" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}">同步失败 {{ $goSyncFailed }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminDashboardPlatformOpsOverviewShouldRenderTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected function loginAsPlatformAdmin(): void
|
||||||
|
{
|
||||||
|
$this->seed();
|
||||||
|
|
||||||
|
$this->post('/admin/login', [
|
||||||
|
'email' => 'platform.admin@demo.local',
|
||||||
|
'password' => 'Platform@123456',
|
||||||
|
])->assertRedirect('/admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_dashboard_should_render_platform_ops_overview_block(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$res = $this->get('/admin');
|
||||||
|
$res->assertOk();
|
||||||
|
|
||||||
|
$html = (string) $res->getContent();
|
||||||
|
|
||||||
|
$this->assertStringContainsString('data-role="dashboard-platform-ops-overview"', $html);
|
||||||
|
$this->assertStringContainsString('平台定位(运营版)', $html);
|
||||||
|
$this->assertStringContainsString('待处理治理(Top3)', $html);
|
||||||
|
$this->assertStringContainsString('可BMPA', $html);
|
||||||
|
$this->assertStringContainsString('可同步', $html);
|
||||||
|
$this->assertStringContainsString('同步失败', $html);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user