From 8bd838c0b661af4c58b8fc162688dea902d92462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Tue, 17 Mar 2026 07:05:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=E5=B9=B3=E5=8F=B0=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=E6=94=B9=E4=B8=BA=E8=BF=90=E8=90=A5=E7=89=88=E5=8C=97?= =?UTF-8?q?=E6=9E=81=E6=98=9F=E6=8C=87=E6=A0=87+=E6=B2=BB=E7=90=86Top3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Admin/DashboardController.php | 108 +++++++++++++++++- resources/views/admin/dashboard.blade.php | 77 +++++++++++-- ...ardPlatformOpsOverviewShouldRenderTest.php | 38 ++++++ 3 files changed, 212 insertions(+), 11 deletions(-) create mode 100644 tests/Feature/AdminDashboardPlatformOpsOverviewShouldRenderTest.php diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php index 2e31dac..7823c99 100644 --- a/app/Http/Controllers/Admin/DashboardController.php +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -159,10 +159,13 @@ class DashboardController extends Controller ] ); + // 统一基准时间:避免本方法内多次 now() 调用在跨天瞬间造成口径漂移 + $baseNow = now(); + // 趋势卡(最小可用):近 7 天平台订单按天统计(订单数 + 已付金额) $trendDays = 7; - $trendStart = now()->startOfDay()->subDays($trendDays - 1); - $trendEnd = now()->endOfDay(); + $trendStart = $baseNow->copy()->startOfDay()->subDays($trendDays - 1); + $trendEnd = $baseNow->copy()->endOfDay(); // 统一提供给视图做“趋势/排行/占比”跳转的日期范围口径,避免 Blade 内重复 now() 计算导致漂移。 $dashboardRangeFrom7d = (string) $trendStart->format('Y-m-d'); @@ -264,6 +267,86 @@ class DashboardController extends Controller $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', [ 'adminName' => $admin->name, 'stats' => $stats, @@ -283,6 +366,27 @@ class DashboardController extends Controller 'store' => config('cache.default'), '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' => [ 'system_role' => '总台管理', 'current_scope' => '总台运营方视角', diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index d951d3f..3c44760 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -462,16 +462,75 @@
说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。
-
-

平台定位

- - - - - - - +
+

平台定位(运营版)

+
只保留“看完知道下一步做什么”的北极星指标与治理积压。
+ + @php + $ops = $platformOpsOverview ?? []; + $opsLinks = (array) ($ops['links'] ?? []); + $paidRevenue30d = (float) ($ops['paid_revenue_30d'] ?? 0); + $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 + +
后台角色{{ $platformOverview['system_role'] }}
当前视角{{ $platformOverview['current_scope'] }}
商家模式{{ $platformOverview['merchant_mode'] }}
渠道数{{ $platformOverview['channel_count'] }}
活跃商家{{ $platformOverview['active_merchants'] }}
待处理订单{{ $platformOverview['pending_orders'] }}
+ + + + + + + + + + + +
近30天已收款 + + ¥{{ number_format($paidRevenue30d, 2) }} + +
活跃付费站点 + + {{ $activePaidMerchants }} + + (以“已生效且未到期订阅”估算) +
续费成功率(30天) + + {{ $renewalRate30d }}% + + ({{ $renewalSuccess30d }} / {{ $renewalCreated30d }}) +
+ +
+
收款漏斗(近7天)
+
用于快速判断卡点:催付 / 治理生效 / 同步订阅。
+ +
+ +
diff --git a/tests/Feature/AdminDashboardPlatformOpsOverviewShouldRenderTest.php b/tests/Feature/AdminDashboardPlatformOpsOverviewShouldRenderTest.php new file mode 100644 index 0000000..65a170f --- /dev/null +++ b/tests/Feature/AdminDashboardPlatformOpsOverviewShouldRenderTest.php @@ -0,0 +1,38 @@ +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); + } +}