ui(dashboard): 平台定位(运营版)加入 mini bar 图形化
This commit is contained in:
@@ -300,6 +300,10 @@ class DashboardController extends Controller
|
|||||||
? min(100, max(0, round(($renewalSuccess30d / $renewalCreated30d) * 100, 1)))
|
? min(100, max(0, round(($renewalSuccess30d / $renewalCreated30d) * 100, 1)))
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
$ordersTotal7d = (int) PlatformOrder::query()
|
||||||
|
->whereBetween('created_at', [$trendStart, $trendEnd])
|
||||||
|
->count();
|
||||||
|
|
||||||
$funnelUnpaidPending7d = (int) PlatformOrder::query()
|
$funnelUnpaidPending7d = (int) PlatformOrder::query()
|
||||||
->whereBetween('created_at', [$trendStart, $trendEnd])
|
->whereBetween('created_at', [$trendStart, $trendEnd])
|
||||||
->where('payment_status', 'unpaid')
|
->where('payment_status', 'unpaid')
|
||||||
@@ -377,6 +381,7 @@ class DashboardController extends Controller
|
|||||||
'renewal_created_30d' => $renewalCreated30d,
|
'renewal_created_30d' => $renewalCreated30d,
|
||||||
|
|
||||||
// 漏斗(近7天)
|
// 漏斗(近7天)
|
||||||
|
'orders_total_7d' => $ordersTotal7d,
|
||||||
'funnel_unpaid_pending_7d' => $funnelUnpaidPending7d,
|
'funnel_unpaid_pending_7d' => $funnelUnpaidPending7d,
|
||||||
'funnel_paid_7d' => $funnelPaid7d,
|
'funnel_paid_7d' => $funnelPaid7d,
|
||||||
'funnel_paid_activated_7d' => $funnelPaidActivated7d,
|
'funnel_paid_activated_7d' => $funnelPaidActivated7d,
|
||||||
|
|||||||
@@ -481,6 +481,8 @@
|
|||||||
$renewalSuccess30d = (int) ($ops['renewal_success_30d'] ?? 0);
|
$renewalSuccess30d = (int) ($ops['renewal_success_30d'] ?? 0);
|
||||||
$renewalCreated30d = (int) ($ops['renewal_created_30d'] ?? 0);
|
$renewalCreated30d = (int) ($ops['renewal_created_30d'] ?? 0);
|
||||||
|
|
||||||
|
$ordersTotal7d = (int) ($ops['orders_total_7d'] ?? 0);
|
||||||
|
|
||||||
$funnelUnpaidPending7d = (int) ($ops['funnel_unpaid_pending_7d'] ?? 0);
|
$funnelUnpaidPending7d = (int) ($ops['funnel_unpaid_pending_7d'] ?? 0);
|
||||||
$funnelPaid7d = (int) ($ops['funnel_paid_7d'] ?? 0);
|
$funnelPaid7d = (int) ($ops['funnel_paid_7d'] ?? 0);
|
||||||
$funnelPaidActivated7d = (int) ($ops['funnel_paid_activated_7d'] ?? 0);
|
$funnelPaidActivated7d = (int) ($ops['funnel_paid_activated_7d'] ?? 0);
|
||||||
@@ -519,23 +521,80 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="mt-10">
|
@php
|
||||||
|
$den = max(1, $ordersTotal7d);
|
||||||
|
$pctUnpaidPending = $den > 0 ? min(100, max(0, round(($funnelUnpaidPending7d / $den) * 100, 1))) : 0;
|
||||||
|
$pctPaid = $den > 0 ? min(100, max(0, round(($funnelPaid7d / $den) * 100, 1))) : 0;
|
||||||
|
$pctPaidActivated = $den > 0 ? min(100, max(0, round(($funnelPaidActivated7d / $den) * 100, 1))) : 0;
|
||||||
|
|
||||||
|
// 待处理治理:以平台订单总量作为分母,给一个“规模感”(不要求精确经营含义)。
|
||||||
|
$poTotalForOps = (int) ($stats['platform_orders'] ?? 0);
|
||||||
|
$denOps = max(1, $poTotalForOps);
|
||||||
|
$pctGoBmpa = $poTotalForOps > 0 ? min(100, max(0, round(($goBmpa / $denOps) * 100, 1))) : 0;
|
||||||
|
$pctGoSyncable = $poTotalForOps > 0 ? min(100, max(0, round(($goSyncable / $denOps) * 100, 1))) : 0;
|
||||||
|
$pctGoSyncFailed = $poTotalForOps > 0 ? min(100, max(0, round(($goSyncFailed / $denOps) * 100, 1))) : 0;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="mt-10" data-role="platform-ops-funnel-bars">
|
||||||
<div class="muted"><strong>收款漏斗(近7天)</strong></div>
|
<div class="muted"><strong>收款漏斗(近7天)</strong></div>
|
||||||
<div class="muted muted-xs mt-6">用于快速判断卡点:催付 / 治理生效 / 同步订阅。</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="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-funnel-unpaid-pending-row" href="{!! (string) ($opsLinks['funnel_unpaid_pending_7d'] ?? $platformOrdersQuickLinks['unpaid_pending']) !!}" aria-label="进入近7天待支付订单集合">
|
||||||
<a class="btn btn-secondary btn-sm" href="{!! (string) ($opsLinks['funnel_paid_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}">已支付 {{ $funnelPaid7d }}</a>
|
<div class="adm-mini-bar-label">待支付</div>
|
||||||
<a class="btn btn-secondary btn-sm" href="{!! (string) ($opsLinks['funnel_paid_activated_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}">已生效 {{ $funnelPaidActivated7d }}</a>
|
<div class="adm-mini-bar" data-role="ops-funnel-unpaid-pending-bar" title="{{ $funnelUnpaidPending7d }} / {{ $ordersTotal7d }}({{ $pctUnpaidPending }}%)">
|
||||||
</div>
|
<span class="adm-mini-bar-fill" style="width: {{ $pctUnpaidPending }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctUnpaidPending }}%({{ $funnelUnpaidPending7d }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-funnel-paid-row" href="{!! (string) ($opsLinks['funnel_paid_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}" aria-label="进入近7天已支付订单集合">
|
||||||
|
<div class="adm-mini-bar-label">已支付</div>
|
||||||
|
<div class="adm-mini-bar" data-role="ops-funnel-paid-bar" title="{{ $funnelPaid7d }} / {{ $ordersTotal7d }}({{ $pctPaid }}%)">
|
||||||
|
<span class="adm-mini-bar-fill" style="width: {{ $pctPaid }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctPaid }}%({{ $funnelPaid7d }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-funnel-paid-activated-row" href="{!! (string) ($opsLinks['funnel_paid_activated_7d'] ?? $platformOrdersQuickLinks['platform_orders']) !!}" aria-label="进入近7天已生效订单集合">
|
||||||
|
<div class="adm-mini-bar-label">已生效</div>
|
||||||
|
<div class="adm-mini-bar" data-role="ops-funnel-paid-activated-bar" title="{{ $funnelPaidActivated7d }} / {{ $ordersTotal7d }}({{ $pctPaidActivated }}%)">
|
||||||
|
<span class="adm-mini-bar-fill" style="width: {{ $pctPaidActivated }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctPaidActivated }}%({{ $funnelPaidActivated7d }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="muted muted-xs mt-6">分母:近7天平台订单总数 {{ $ordersTotal7d }}(含未支付/已支付)。</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10" data-role="platform-ops-governance-bars">
|
||||||
<div class="muted"><strong>待处理治理(Top3)</strong></div>
|
<div class="muted"><strong>待处理治理(Top3)</strong></div>
|
||||||
<div class="actions gap-10 mt-6">
|
<div class="muted muted-xs mt-6">用于快速判断治理积压:优先清空“能直接处理”的集合。</div>
|
||||||
<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="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-govern-bmpa-row" href="{!! $platformOrdersQuickLinks['unpaid_pending'] !!}" aria-label="进入可BMPA处理订单集合">
|
||||||
<a class="btn btn-sm" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}">同步失败 {{ $goSyncFailed }}</a>
|
<div class="adm-mini-bar-label">可BMPA</div>
|
||||||
</div>
|
<div class="adm-mini-bar" data-role="ops-govern-bmpa-bar" title="{{ $goBmpa }} / {{ $poTotalForOps }}({{ $pctGoBmpa }}%)">
|
||||||
|
<span class="adm-mini-bar-fill" style="width: {{ $pctGoBmpa }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctGoBmpa }}%({{ $goBmpa }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-govern-syncable-row" href="{!! $platformOrdersQuickLinks['syncable_only'] !!}" aria-label="进入可同步订单集合">
|
||||||
|
<div class="adm-mini-bar-label">可同步</div>
|
||||||
|
<div class="adm-mini-bar" data-role="ops-govern-syncable-bar" title="{{ $goSyncable }} / {{ $poTotalForOps }}({{ $pctGoSyncable }}%)">
|
||||||
|
<span class="adm-mini-bar-fill" style="width: {{ $pctGoSyncable }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctGoSyncable }}%({{ $goSyncable }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-govern-sync-failed-row" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}" aria-label="进入同步失败订单集合">
|
||||||
|
<div class="adm-mini-bar-label">同步失败</div>
|
||||||
|
<div class="adm-mini-bar" data-role="ops-govern-sync-failed-bar" title="{{ $goSyncFailed }} / {{ $poTotalForOps }}({{ $pctGoSyncFailed }}%)">
|
||||||
|
<span class="adm-mini-bar-fill" style="width: {{ $pctGoSyncFailed }}%"></span>
|
||||||
|
</div>
|
||||||
|
<div class="adm-mini-bar-value">{{ $pctGoSyncFailed }}%({{ $goSyncFailed }})</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="muted muted-xs mt-6">分母:平台订单总量 {{ $poTotalForOps }}(用于规模感,不作为经营口径)。</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminDashboardPlatformOpsOverviewMiniBarsShouldRenderTest 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_platform_ops_overview_should_render_mini_bars(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$res = $this->get('/admin');
|
||||||
|
$res->assertOk();
|
||||||
|
|
||||||
|
$html = (string) $res->getContent();
|
||||||
|
|
||||||
|
// 收款漏斗 mini bars
|
||||||
|
$this->assertStringContainsString('data-role="platform-ops-funnel-bars"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-funnel-unpaid-pending-bar"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-funnel-paid-bar"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-funnel-paid-activated-bar"', $html);
|
||||||
|
|
||||||
|
// 待处理治理 Top3 mini bars
|
||||||
|
$this->assertStringContainsString('data-role="platform-ops-governance-bars"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-govern-bmpa-bar"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-govern-syncable-bar"', $html);
|
||||||
|
$this->assertStringContainsString('data-role="ops-govern-sync-failed-bar"', $html);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user