From 66a8319f0af29a49420f9dc6ae242e77e7284acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Tue, 17 Mar 2026 07:27:15 +0800 Subject: [PATCH] =?UTF-8?q?ui(dashboard):=20=E5=B9=B3=E5=8F=B0=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=EF=BC=88=E8=BF=90=E8=90=A5=E7=89=88=EF=BC=89=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=20mini=20bar=20=E5=9B=BE=E5=BD=A2=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Admin/DashboardController.php | 5 ++ resources/views/admin/dashboard.blade.php | 83 ++++++++++++++++--- ...ormOpsOverviewMiniBarsShouldRenderTest.php | 43 ++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 tests/Feature/AdminDashboardPlatformOpsOverviewMiniBarsShouldRenderTest.php diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index 6f5e09c..003723c 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -300,6 +300,10 @@ class DashboardController extends Controller ? min(100, max(0, round(($renewalSuccess30d / $renewalCreated30d) * 100, 1))) : 0; + $ordersTotal7d = (int) PlatformOrder::query() + ->whereBetween('created_at', [$trendStart, $trendEnd]) + ->count(); + $funnelUnpaidPending7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'unpaid') @@ -377,6 +381,7 @@ class DashboardController extends Controller 'renewal_created_30d' => $renewalCreated30d, // 漏斗(近7天) + 'orders_total_7d' => $ordersTotal7d, 'funnel_unpaid_pending_7d' => $funnelUnpaidPending7d, 'funnel_paid_7d' => $funnelPaid7d, 'funnel_paid_activated_7d' => $funnelPaidActivated7d, diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index f90ff2f..b3a6e66 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -481,6 +481,8 @@ $renewalSuccess30d = (int) ($ops['renewal_success_30d'] ?? 0); $renewalCreated30d = (int) ($ops['renewal_created_30d'] ?? 0); + $ordersTotal7d = (int) ($ops['orders_total_7d'] ?? 0); + $funnelUnpaidPending7d = (int) ($ops['funnel_unpaid_pending_7d'] ?? 0); $funnelPaid7d = (int) ($ops['funnel_paid_7d'] ?? 0); $funnelPaidActivated7d = (int) ($ops['funnel_paid_activated_7d'] ?? 0); @@ -519,23 +521,80 @@ -
+ @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 + + -
+
待处理治理(Top3)
- +
用于快速判断治理积压:优先清空“能直接处理”的集合。
+ + +
可BMPA
+
+ +
+
{{ $pctGoBmpa }}%({{ $goBmpa }})
+
+ + +
可同步
+
+ +
+
{{ $pctGoSyncable }}%({{ $goSyncable }})
+
+ + +
同步失败
+
+ +
+
{{ $pctGoSyncFailed }}%({{ $goSyncFailed }})
+
+ +
分母:平台订单总量 {{ $poTotalForOps }}(用于规模感,不作为经营口径)。
diff --git a/tests/Feature/AdminDashboardPlatformOpsOverviewMiniBarsShouldRenderTest.php b/tests/Feature/AdminDashboardPlatformOpsOverviewMiniBarsShouldRenderTest.php new file mode 100644 index 0000000..1eddc75 --- /dev/null +++ b/tests/Feature/AdminDashboardPlatformOpsOverviewMiniBarsShouldRenderTest.php @@ -0,0 +1,43 @@ +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); + } +}