Files
saasshop/resources/views/admin/dashboard.blade.php

1048 lines
66 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.layouts.app')
@section('title', '总台仪表盘')
@section('page_title', '总台仪表盘')
@section('content')
@php
// 用于构建“从仪表盘跳到其它治理页后可返回仪表盘”的 back
$selfWithoutBack = \App\Support\BackUrl::selfWithoutBack();
// 重要:从仪表盘跳转到治理页时,应默认返回“仪表盘”本身(而不是沿用进入仪表盘时的 back
$billingEntryLinks = [
'platform_orders' => \App\Support\BackUrl::withBack('/admin/platform-orders', $selfWithoutBack),
'site_subscriptions' => \App\Support\BackUrl::withBack('/admin/site-subscriptions', $selfWithoutBack),
'plans' => \App\Support\BackUrl::withBack('/admin/plans', $selfWithoutBack),
];
// 仪表盘内所有“近7天”口径统一从 Controller 注入(避免 Blade 内 now() 计算导致跨天漂移)。
$rangeFrom7d = (string) ($dashboardRangeFrom7d ?? now()->subDays(6)->format('Y-m-d'));
$rangeTo7d = (string) ($dashboardRangeTo7d ?? now()->format('Y-m-d'));
$platformOrdersQuickLinks = [
// 复用工作台入口(避免 /admin/platform-orders 入口口径分叉)
'platform_orders' => $billingEntryLinks['platform_orders'],
// 时间范围集合:用于趋势/排行/占比跳转(避免各处散落拼接 query 导致口径漂移)
'platform_orders_range' => function (string $from, string $to) use ($selfWithoutBack): string {
return \App\Support\BackUrl::withBack(
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
'created_from' => $from,
'created_to' => $to,
]),
$selfWithoutBack
);
},
// 站点收入排行已付订单集合merchant_id + payment_status=paid + 日期范围)
'merchant_paid_orders_range' => function (int $merchantId, string $from, string $to) use ($selfWithoutBack): string {
return \App\Support\BackUrl::withBack(
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
'merchant_id' => $merchantId,
'payment_status' => 'paid',
'created_from' => $from,
'created_to' => $to,
]),
$selfWithoutBack
);
},
// 套餐订单占比:按套餐筛选 + 日期范围(用于 Top5 表格链接,同时被 JS 复用为 mini chart 点击入口)
'plan_orders_range' => function (int $planId, string $from, string $to) use ($selfWithoutBack): string {
return \App\Support\BackUrl::withBack(
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
'plan_id' => $planId,
'created_from' => $from,
'created_to' => $to,
]),
$selfWithoutBack
);
},
// 平台订单(收费闭环)工作台入口:尽量保持与列表页筛选语义一致。
'unpaid_pending' => \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=unpaid&status=pending', $selfWithoutBack),
// 待生效paid + pending并显式锁定 sync_status=unsynced排除同步失败等异常单
'paid_pending' => \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=paid&status=pending&sync_status=unsynced', $selfWithoutBack),
// 可同步(工作台口径):只看可同步 + 未同步(排除同步失败等异常单),与工作台统计口径一致。
'syncable_only' => \App\Support\BackUrl::withBack('/admin/platform-orders?syncable_only=1&sync_status=unsynced', $selfWithoutBack),
'sync_failed' => \App\Support\BackUrl::withBack('/admin/platform-orders?sync_status=failed', $selfWithoutBack),
'renewal_missing_subscription' => \App\Support\BackUrl::withBack('/admin/platform-orders?renewal_missing_subscription=1', $selfWithoutBack),
'bmpa_failed' => \App\Support\BackUrl::withBack('/admin/platform-orders?bmpa_failed_only=1', $selfWithoutBack),
'paid_no_receipt' => \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=paid&receipt_status=none', $selfWithoutBack),
'reconcile_mismatch' => \App\Support\BackUrl::withBack('/admin/platform-orders?reconcile_mismatch=1', $selfWithoutBack),
'refund_inconsistent' => \App\Support\BackUrl::withBack('/admin/platform-orders?refund_inconsistent=1', $selfWithoutBack),
];
$subscriptionQuickLinks = [
// 复用工作台入口(避免 /admin/site-subscriptions 入口口径分叉)
'site_subscriptions' => $billingEntryLinks['site_subscriptions'],
'expiring_7d' => \App\Support\BackUrl::withBack('/admin/site-subscriptions?expiry=expiring_7d', $selfWithoutBack),
'expired' => \App\Support\BackUrl::withBack('/admin/site-subscriptions?expiry=expired', $selfWithoutBack),
];
@endphp
<div data-page="admin.dashboard">
<div class="page-header mb-20">
<div class="page-header-main">
<div>
<div class="page-header-title">总台仪表盘</div>
<div class="page-header-subtitle">欢迎回来,{{ $adminName }}。这里是总台管理(平台运营方)的全局总览入口(聚焦:收费闭环与治理动作可达)。</div>
</div>
<div class="page-header-actions">
<a class="btn btn-secondary btn-sm" href="#billing-workbench">跳到收费工作台</a>
</div>
</div>
<div class="page-header-meta">
<div>统计缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}</div>
<div>当前视角:{{ $platformOverview['current_scope'] }}</div>
<div>活跃站点:{{ $platformOverview['active_merchants'] }}</div>
<div>待处理订单:{{ $platformOverview['pending_orders'] }}</div>
</div>
</div>
<div class="kpi-grid mb-20" data-role="kpi-grid">
@php
$kpiLinks = [
'merchants' => \App\Support\BackUrl::withBack('/admin/merchants', $selfWithoutBack),
'plans' => $billingEntryLinks['plans'],
'site_subscriptions' => $billingEntryLinks['site_subscriptions'],
'platform_orders' => $billingEntryLinks['platform_orders'],
];
@endphp
<div class="card">
<div class="stat-card-title">站点</div>
<div class="stat-card-value"><a class="link" href="{!! $kpiLinks['merchants'] !!}">{{ $stats['merchants'] }}</a></div>
<div class="stat-card-footnote">全站点数量(总台视角)</div>
</div>
<div class="card">
<div class="stat-card-title">套餐</div>
<div class="stat-card-value"><a class="link" href="{!! $kpiLinks['plans'] !!}">{{ (int) ($stats['plans'] ?? 0) }}</a></div>
<div class="stat-card-footnote">可售套餐目录</div>
</div>
<div class="card">
<div class="stat-card-title">订阅</div>
<div class="stat-card-value"><a class="link" href="{!! $kpiLinks['site_subscriptions'] !!}">{{ (int) ($stats['site_subscriptions'] ?? 0) }}</a></div>
<div class="stat-card-footnote">站点订阅总量(收费主链)</div>
</div>
<div class="card">
<div class="stat-card-title">平台订单</div>
<div class="stat-card-value"><a class="link" href="{!! $kpiLinks['platform_orders'] !!}">{{ (int) ($stats['platform_orders'] ?? 0) }}</a></div>
<div class="stat-card-footnote">平台收费订单总量</div>
</div>
</div>
<div class="two-col mb-20" data-role="analysis-skeleton" data-eq-col="recent-platform-orders">
<div class="card" data-role="dashboard-card-trend">
<h3 class="mt-0">趋势</h3>
<div class="muted">近7天平台订单按天</div>
@php
$trendRows = (array) ($platformOrderTrend7d ?? []);
// 用于前端渐进增强渲染迷你图表JS 读取 data-points
$trendPoints = [];
foreach ($trendRows as $r) {
$trendPoints[] = [
'date' => (string) ($r['date'] ?? ''),
'count' => (int) ($r['count'] ?? 0),
'paid_sum' => (float) ($r['paid_sum'] ?? 0),
];
}
@endphp
<div class="adm-mini-chart" data-role="platform-order-trend-7d-chart" data-points='@json($trendPoints)'></div>
@php
$trendPaidTotal = 0.0;
$trendOrdersTotal = 0;
$trendPaidMax = 0.0;
foreach ($trendRows as $r) {
$trendPaidTotal += (float) ($r['paid_sum'] ?? 0);
$trendOrdersTotal += (int) ($r['count'] ?? 0);
$trendPaidMax = max($trendPaidMax, (float) ($r['paid_sum'] ?? 0));
}
@endphp
<div class="adm-mini-meta" data-role="platform-order-trend-7d-meta">
<span class="adm-mini-meta-item">7天合计已付:<strong>¥{{ number_format($trendPaidTotal, 2) }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">7天订单数:<strong>{{ (int) $trendOrdersTotal }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">峰值:<strong>¥{{ number_format($trendPaidMax, 2) }}</strong></span>
</div>
<table class="mt-10" data-role="platform-order-trend-7d">
<thead>
<tr>
<th>日期</th>
<th>订单数</th>
<th>已付金额</th>
</tr>
</thead>
<tbody>
@forelse($trendRows as $row)
<tr>
@php
$d = (string) ($row['date'] ?? '');
$dayOrdersUrl = ($platformOrdersQuickLinks['platform_orders_range'])($d, $d);
@endphp
<td><a class="link" href="{!! $dayOrdersUrl !!}">{{ $d }}</a></td>
<td>{{ (int) ($row['count'] ?? 0) }}</td>
<td>¥{{ number_format((float) ($row['paid_sum'] ?? 0), 2) }}</td>
</tr>
@empty
<tr>
<td colspan="3" class="muted">暂无数据</td>
</tr>
@endforelse
</tbody>
</table>
<div class="muted muted-xs mt-10">说明:先接入最小可用趋势数据;后续再补时间范围切换、维度切换与可视化图表。</div>
</div>
<div class="card">
<h3 class="mt-0">排行近7天站点收入 Top5</h3>
@php
$rankRows = (array) ($merchantRevenueRank7d ?? []);
$rankTotal = 0.0;
foreach ($rankRows as $r) {
$rankTotal += (float) ($r['paid_sum'] ?? 0);
}
@endphp
<div class="muted">近7天按已付金额统计</div>
@php
// 用于前端渐进增强渲染迷你排行JS 读取 data-points
$rankPoints = [];
foreach ($rankRows as $r) {
$mid = (int) ($r['merchant_id'] ?? 0);
$rankPoints[] = [
'merchant_id' => $mid,
'name' => (string) (($merchantIdToName[$mid] ?? '') ?: ('#' . $mid)),
'count' => (int) ($r['count'] ?? 0),
'paid_sum' => (float) ($r['paid_sum'] ?? 0),
];
}
@endphp
<div class="adm-mini-rank" data-role="merchant-revenue-rank-7d-chart" data-points='@json($rankPoints)'></div>
@php
$rankPaidMax = 0.0;
$rankOrdersTotal = 0;
foreach ($rankRows as $r) {
$rankPaidMax = max($rankPaidMax, (float) ($r['paid_sum'] ?? 0));
$rankOrdersTotal += (int) ($r['count'] ?? 0);
}
$rankTotalPaidAll = (float) ($merchantRevenueTotalPaid7d ?? 0);
if ($rankTotalPaidAll <= 0) {
$rankTotalPaidAll = (float) $rankTotal;
}
$rankCoveragePct = $rankTotalPaidAll > 0 ? round(((float) $rankTotal / $rankTotalPaidAll) * 100, 1) : 0;
$rankOtherPaid = max(0.0, $rankTotalPaidAll - (float) $rankTotal);
$rankOtherPct = max(0.0, round(100 - $rankCoveragePct, 1));
@endphp
<div class="adm-mini-meta" data-role="merchant-revenue-rank-7d-meta">
<span class="adm-mini-meta-item">Top5合计已付<strong>¥{{ number_format($rankTotal, 2) }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">Top5订单数<strong>{{ (int) $rankOrdersTotal }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">覆盖率:<strong>{{ $rankCoveragePct }}%</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">其它:<strong>{{ $rankOtherPct }}%</strong>(¥{{ number_format($rankOtherPaid, 2) }}</span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">Top1金额<strong>¥{{ number_format($rankPaidMax, 2) }}</strong></span>
</div>
<table class="mt-10" data-role="merchant-revenue-rank-7d">
<thead>
<tr>
<th>站点</th>
<th>订单数</th>
<th>已付金额</th>
</tr>
</thead>
<tbody>
@forelse($rankRows as $row)
@php
$mid = (int) ($row['merchant_id'] ?? 0);
$mname = (string) (($merchantIdToName[$mid] ?? '') ?: ('#' . $mid));
$merchantOrdersUrl = ($platformOrdersQuickLinks['merchant_paid_orders_range'])($mid, $rangeFrom7d, $rangeTo7d);
@endphp
<tr>
<td><a class="link" href="{!! $merchantOrdersUrl !!}">{{ $mname }}</a></td>
<td>{{ (int) ($row['count'] ?? 0) }}</td>
<td>¥{{ number_format((float) ($row['paid_sum'] ?? 0), 2) }}</td>
</tr>
@empty
<tr>
<td colspan="3" class="muted">暂无数据</td>
</tr>
@endforelse
</tbody>
</table>
<div class="muted muted-xs mt-10">说明:先落最小可用 Top5 排行;后续补时间范围切换、维度切换与异常排行。</div>
</div>
</div>
<div class="two-col mb-20" data-eq-col="recent-platform-orders">
<div class="card" id="billing-workbench" data-role="dashboard-card-billing-workbench">
<h3 class="mt-0">收费工作台(快捷治理)</h3>
<div class="muted">聚焦收费闭环的日常治理入口:订单 订阅 套餐。</div>
<div class="actions mt-10">
<a class="btn btn-sm" href="{!! $billingEntryLinks['platform_orders'] !!}">平台订单</a>
<a class="btn btn-secondary btn-sm" href="{!! $billingEntryLinks['site_subscriptions'] !!}">订阅</a>
<a class="btn btn-secondary btn-sm" href="{!! $billingEntryLinks['plans'] !!}">套餐</a>
</div>
<div class="mt-12">
<div class="muted">平台订单快捷筛选(只保留“点完能做事”的治理入口):</div>
<div class="actions mt-8" data-role="dashboard-po-quick-links">
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-bmpa-processable" href="{!! $platformOrdersQuickLinks['unpaid_pending'] !!}">可BMPA处理{{ (int) ($stats['platform_orders_unpaid_pending'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-syncable" href="{!! $platformOrdersQuickLinks['syncable_only'] !!}">可同步({{ (int) ($stats['platform_orders_syncable'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-sync-failed" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}">同步失败({{ (int) ($stats['platform_orders_sync_failed'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-no-receipt" href="{!! $platformOrdersQuickLinks['paid_no_receipt'] !!}">无回执({{ (int) ($stats['platform_orders_paid_no_receipt'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-reconcile-mismatch" href="{!! $platformOrdersQuickLinks['reconcile_mismatch'] !!}">对账不一致({{ (int) ($stats['platform_orders_reconcile_mismatch'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" data-role="dashboard-po-quicklink-refund-inconsistent" href="{!! $platformOrdersQuickLinks['refund_inconsistent'] !!}">退款不一致({{ (int) ($stats['platform_orders_refund_inconsistent'] ?? 0) }}</a>
</div>
<details class="mt-10" data-role="dashboard-po-advanced-links" data-storage-key="admin.dashboard.po_advanced_links">
<summary class="muted">高级筛选(少用,默认收起)</summary>
<div class="actions mt-8">
<a class="btn btn-secondary btn-sm" href="{!! $platformOrdersQuickLinks['paid_pending'] !!}">待生效({{ (int) ($stats['platform_orders_paid_pending'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" href="{!! $platformOrdersQuickLinks['renewal_missing_subscription'] !!}">续费缺订阅({{ (int) ($stats['platform_orders_renewal_missing_subscription'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" href="{!! $platformOrdersQuickLinks['bmpa_failed'] !!}">BMPA失败{{ (int) ($stats['platform_orders_bmpa_failed'] ?? 0) }}</a>
</div>
</details>
@php
$poTotal = (int) ($stats['platform_orders'] ?? 0);
$poSyncFailed = (int) ($stats['platform_orders_sync_failed'] ?? 0);
$poBmpaFailed = (int) ($stats['platform_orders_bmpa_failed'] ?? 0);
$poNoReceipt = (int) ($stats['platform_orders_paid_no_receipt'] ?? 0);
$poRenewalMissing = (int) ($stats['platform_orders_renewal_missing_subscription'] ?? 0);
$poReconcileMismatch = (int) ($stats['platform_orders_reconcile_mismatch'] ?? 0);
$poRefundInconsistent = (int) ($stats['platform_orders_refund_inconsistent'] ?? 0);
$poSyncFailedPct = $poTotal > 0 ? min(100, max(0, round(($poSyncFailed / $poTotal) * 100, 1))) : 0;
$poBmpaFailedPct = $poTotal > 0 ? min(100, max(0, round(($poBmpaFailed / $poTotal) * 100, 1))) : 0;
$poNoReceiptPct = $poTotal > 0 ? min(100, max(0, round(($poNoReceipt / $poTotal) * 100, 1))) : 0;
$poRenewalMissingPct = $poTotal > 0 ? min(100, max(0, round(($poRenewalMissing / $poTotal) * 100, 1))) : 0;
$poReconcileMismatchPct = $poTotal > 0 ? min(100, max(0, round(($poReconcileMismatch / $poTotal) * 100, 1))) : 0;
$poRefundInconsistentPct = $poTotal > 0 ? min(100, max(0, round(($poRefundInconsistent / $poTotal) * 100, 1))) : 0;
@endphp
@php
$poUnpaidPending = (int) ($stats['platform_orders_unpaid_pending'] ?? 0);
$poPaidPending = (int) ($stats['platform_orders_paid_pending'] ?? 0);
$poSyncable = (int) ($stats['platform_orders_syncable'] ?? 0);
$poUnpaidPendingPct = $poTotal > 0 ? min(100, max(0, round(($poUnpaidPending / $poTotal) * 100, 1))) : 0;
$poPaidPendingPct = $poTotal > 0 ? min(100, max(0, round(($poPaidPending / $poTotal) * 100, 1))) : 0;
$poSyncablePct = $poTotal > 0 ? min(100, max(0, round(($poSyncable / $poTotal) * 100, 1))) : 0;
@endphp
<div class="mt-10" data-role="dashboard-po-funnel-bars">
<div class="muted muted-xs">收费主链漏斗(相对平台订单总量 {{ $poTotal }}</div>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-unpaid-pending-row" href="{!! $platformOrdersQuickLinks['unpaid_pending'] !!}" aria-label="进入待支付订单集合">
<div class="adm-mini-bar-label">待支付</div>
<div class="adm-mini-bar" data-role="dashboard-po-unpaid-pending-bar" title="{{ $poUnpaidPending }} / {{ $poTotal }}{{ $poUnpaidPendingPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poUnpaidPendingPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poUnpaidPendingPct }}%{{ $poUnpaidPending }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-paid-pending-row" href="{!! $platformOrdersQuickLinks['paid_pending'] !!}" aria-label="进入待生效订单集合">
<div class="adm-mini-bar-label">待生效</div>
<div class="adm-mini-bar" data-role="dashboard-po-paid-pending-bar" title="{{ $poPaidPending }} / {{ $poTotal }}{{ $poPaidPendingPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poPaidPendingPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poPaidPendingPct }}%{{ $poPaidPending }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-syncable-row" href="{!! $platformOrdersQuickLinks['syncable_only'] !!}" aria-label="进入可同步订单集合">
<div class="adm-mini-bar-label">可同步</div>
<div class="adm-mini-bar" data-role="dashboard-po-syncable-bar" title="{{ $poSyncable }} / {{ $poTotal }}{{ $poSyncablePct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poSyncablePct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poSyncablePct }}%{{ $poSyncable }}</div>
</a>
</div>
<div class="mt-10" data-role="dashboard-po-governance-bars">
<div class="muted muted-xs">治理风险占比(相对平台订单总量 {{ $poTotal }}</div>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-sync-failed-row" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}" aria-label="进入同步失败订单集合">
<div class="adm-mini-bar-label">同步失败</div>
<div class="adm-mini-bar" data-role="dashboard-po-sync-failed-bar" title="{{ $poSyncFailed }} / {{ $poTotal }}{{ $poSyncFailedPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poSyncFailedPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poSyncFailedPct }}%{{ $poSyncFailed }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-bmpa-failed-row" href="{!! $platformOrdersQuickLinks['bmpa_failed'] !!}" aria-label="进入BMPA失败订单集合">
<div class="adm-mini-bar-label">BMPA失败</div>
<div class="adm-mini-bar" data-role="dashboard-po-bmpa-failed-bar" title="{{ $poBmpaFailed }} / {{ $poTotal }}{{ $poBmpaFailedPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poBmpaFailedPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poBmpaFailedPct }}%{{ $poBmpaFailed }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-no-receipt-row" href="{!! $platformOrdersQuickLinks['paid_no_receipt'] !!}" aria-label="进入无回执订单集合">
<div class="adm-mini-bar-label">无回执</div>
<div class="adm-mini-bar" data-role="dashboard-po-no-receipt-bar" title="{{ $poNoReceipt }} / {{ $poTotal }}{{ $poNoReceiptPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poNoReceiptPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poNoReceiptPct }}%{{ $poNoReceipt }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-renewal-missing-row" href="{!! $platformOrdersQuickLinks['renewal_missing_subscription'] !!}" aria-label="进入续费缺订阅订单集合">
<div class="adm-mini-bar-label">续费缺订阅</div>
<div class="adm-mini-bar" data-role="dashboard-po-renewal-missing-bar" title="{{ $poRenewalMissing }} / {{ $poTotal }}{{ $poRenewalMissingPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poRenewalMissingPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poRenewalMissingPct }}%{{ $poRenewalMissing }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-reconcile-mismatch-row" href="{!! $platformOrdersQuickLinks['reconcile_mismatch'] !!}" aria-label="进入对账不一致订单集合">
<div class="adm-mini-bar-label">对账不一致</div>
<div class="adm-mini-bar" data-role="dashboard-po-reconcile-mismatch-bar" title="{{ $poReconcileMismatch }} / {{ $poTotal }}{{ $poReconcileMismatchPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poReconcileMismatchPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poReconcileMismatchPct }}%{{ $poReconcileMismatch }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-po-refund-inconsistent-row" href="{!! $platformOrdersQuickLinks['refund_inconsistent'] !!}" aria-label="进入退款不一致订单集合">
<div class="adm-mini-bar-label">退款不一致</div>
<div class="adm-mini-bar" data-role="dashboard-po-refund-inconsistent-bar" title="{{ $poRefundInconsistent }} / {{ $poTotal }}{{ $poRefundInconsistentPct }}%">
<span class="adm-mini-bar-fill" style="width: {{ $poRefundInconsistentPct }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $poRefundInconsistentPct }}%{{ $poRefundInconsistent }}</div>
</a>
</div>
</div>
<div class="mt-12">
<div class="muted">订阅到期治理:</div>
<div class="actions mt-8">
<a class="btn btn-secondary btn-sm" href="{!! $subscriptionQuickLinks['expiring_7d'] !!}">7天内到期({{ (int) ($stats['site_subscriptions_expiring_7d'] ?? 0) }}</a>
<a class="btn btn-secondary btn-sm" href="{!! $subscriptionQuickLinks['expired'] !!}">已过期({{ (int) ($stats['site_subscriptions_expired'] ?? 0) }}</a>
</div>
@php
$subTotal = (int) ($stats['site_subscriptions'] ?? 0);
$subExpiring7d = (int) ($stats['site_subscriptions_expiring_7d'] ?? 0);
$subExpired = (int) ($stats['site_subscriptions_expired'] ?? 0);
$pctExpiring7d = $subTotal > 0 ? min(100, max(0, round(($subExpiring7d / $subTotal) * 100, 1))) : 0;
$pctExpired = $subTotal > 0 ? min(100, max(0, round(($subExpired / $subTotal) * 100, 1))) : 0;
@endphp
<div class="mt-10" data-role="dashboard-sub-expiry-bars">
<div class="muted muted-xs">占比(相对订阅总量 {{ $subTotal }}</div>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-sub-expiring-7d-row" href="{!! $subscriptionQuickLinks['expiring_7d'] !!}" aria-label="进入7天内到期订阅集合">
<div class="adm-mini-bar-label">7天内到期</div>
<div class="adm-mini-bar" data-role="dashboard-sub-expiring-7d-bar" title="{{ $subExpiring7d }} / {{ $subTotal }}{{ $pctExpiring7d }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctExpiring7d }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctExpiring7d }}%{{ $subExpiring7d }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="dashboard-sub-expired-row" href="{!! $subscriptionQuickLinks['expired'] !!}" aria-label="进入已过期订阅集合">
<div class="adm-mini-bar-label">已过期</div>
<div class="adm-mini-bar" data-role="dashboard-sub-expired-bar" title="{{ $subExpired }} / {{ $subTotal }}{{ $pctExpired }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctExpired }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctExpired }}%{{ $subExpired }}</div>
</a>
</div>
</div>
<div class="muted muted-xs mt-10">说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。</div>
</div>
<div class="card" data-role="dashboard-platform-ops-overview">
<h3 class="mt-0">平台定位(运营版)</h3>
<div class="muted">只保留“看完知道下一步做什么”的北极星指标与治理积压。</div>
@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);
$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);
$goBmpa = (int) ($ops['govern_bmpa_processable'] ?? 0);
$goSyncable = (int) ($ops['govern_syncable'] ?? 0);
$goSyncFailed = (int) ($ops['govern_sync_failed'] ?? 0);
@endphp
<details class="mt-10" data-role="platform-ops-northstar-details" data-storage-key="admin.dashboard.platform_ops_northstar_details">
<summary class="muted"><strong>北极星指标(文字口径)</strong> <span class="muted muted-xs">(少用,默认收起)</span></summary>
<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>
</details>
@php
// 北极星指标图形化:给运营“规模感/健康感”,但不引入额外分类维度。
$merchantsTotal = (int) ($stats['merchants'] ?? 0);
$pctActivePaidMerchants = $merchantsTotal > 0 ? min(100, max(0, round(($activePaidMerchants / $merchantsTotal) * 100, 1))) : 0;
@endphp
<div class="mt-10" data-role="platform-ops-northstar-bars">
<div class="muted"><strong>北极星指标(图形化)</strong></div>
@include('admin.components.mini_bar_row', [
'class' => 'mt-6',
'rowRole' => 'ops-northstar-active-paid-merchants-row',
'barRole' => 'ops-northstar-active-paid-merchants-bar',
'href' => (string) ($opsLinks['active_paid_merchants_subscriptions'] ?? $billingEntryLinks['site_subscriptions']),
'ariaLabel' => '进入活跃付费站点订阅集合',
'label' => '付费渗透',
'pct' => $pctActivePaidMerchants,
'title' => $activePaidMerchants . ' / ' . $merchantsTotal . '' . $pctActivePaidMerchants . '%',
'value' => $pctActivePaidMerchants . '%' . $activePaidMerchants . '',
])
@include('admin.components.mini_bar_row', [
'class' => 'mt-6',
'rowRole' => 'ops-northstar-renewal-rate-row',
'barRole' => 'ops-northstar-renewal-rate-bar',
'href' => (string) ($opsLinks['renewal_orders_30d'] ?? $billingEntryLinks['platform_orders']),
'ariaLabel' => '进入近30天续费订单集合',
'label' => '续费成功率',
'pct' => $renewalRate30d,
'title' => $renewalRate30d . '%' . $renewalSuccess30d . ' / ' . $renewalCreated30d . '',
'value' => $renewalRate30d . '%',
])
<div class="muted muted-xs mt-6">说明:付费渗透 = 活跃付费站点 / 站点总数续费成功率按近30天续费单计算。</div>
</div>
@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 muted-xs mt-6">用于快速判断卡点:催付 / 治理生效 / 同步订阅。</div>
@include('admin.components.mini_bar_row', [
'class' => 'mt-6',
'rowRole' => 'ops-funnel-unpaid-pending-row',
'barRole' => 'ops-funnel-unpaid-pending-bar',
'href' => (string) ($opsLinks['funnel_unpaid_pending_7d'] ?? $platformOrdersQuickLinks['unpaid_pending']),
'ariaLabel' => '进入近7天待支付订单集合',
'label' => '待支付',
'pct' => $pctUnpaidPending,
'title' => $funnelUnpaidPending7d . ' / ' . $ordersTotal7d . '' . $pctUnpaidPending . '%',
'value' => $pctUnpaidPending . '%' . $funnelUnpaidPending7d . '',
])
@include('admin.components.mini_bar_row', [
'class' => 'mt-6',
'rowRole' => 'ops-funnel-paid-row',
'barRole' => 'ops-funnel-paid-bar',
'href' => (string) ($opsLinks['funnel_paid_7d'] ?? $platformOrdersQuickLinks['platform_orders']),
'ariaLabel' => '进入近7天已支付订单集合',
'label' => '已支付',
'pct' => $pctPaid,
'title' => $funnelPaid7d . ' / ' . $ordersTotal7d . '' . $pctPaid . '%',
'value' => $pctPaid . '%' . $funnelPaid7d . '',
])
@include('admin.components.mini_bar_row', [
'class' => 'mt-6',
'rowRole' => 'ops-funnel-paid-activated-row',
'barRole' => 'ops-funnel-paid-activated-bar',
'href' => (string) ($opsLinks['funnel_paid_activated_7d'] ?? $platformOrdersQuickLinks['platform_orders']),
'ariaLabel' => '进入近7天已生效订单集合',
'label' => '已生效',
'pct' => $pctPaidActivated,
'title' => $funnelPaidActivated7d . ' / ' . $ordersTotal7d . '' . $pctPaidActivated . '%',
'value' => $pctPaidActivated . '%' . $funnelPaidActivated7d . '',
])
<div class="muted muted-xs mt-6">分母近7天平台订单总数 {{ $ordersTotal7d }}(含未支付/已支付)。</div>
</div>
<div class="mt-10" data-role="platform-ops-governance-bars">
<div class="muted"><strong>待处理治理Top3</strong></div>
<div class="muted muted-xs mt-6">用于快速判断治理积压:优先清空“能直接处理”的集合。</div>
<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处理订单集合">
<div class="adm-mini-bar-label">可BMPA</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>
@php
// 平台健康预警:原因型风险(点进去有明确动作:补回执/对账/核对退款)。
$riskNoReceipt = (int) ($stats['platform_orders_paid_no_receipt'] ?? 0);
$riskReconcileMismatch = (int) ($stats['platform_orders_reconcile_mismatch'] ?? 0);
$riskRefundInconsistent = (int) ($stats['platform_orders_refund_inconsistent'] ?? 0);
$pctRiskNoReceipt = $poTotalForOps > 0 ? min(100, max(0, round(($riskNoReceipt / $denOps) * 100, 1))) : 0;
$pctRiskReconcileMismatch = $poTotalForOps > 0 ? min(100, max(0, round(($riskReconcileMismatch / $denOps) * 100, 1))) : 0;
$pctRiskRefundInconsistent = $poTotalForOps > 0 ? min(100, max(0, round(($riskRefundInconsistent / $denOps) * 100, 1))) : 0;
// 异常积压:点进去要做“治理修复/补关联/重试”。
$exBmpaFailed = (int) ($stats['platform_orders_bmpa_failed'] ?? 0);
$exRenewalMissing = (int) ($stats['platform_orders_renewal_missing_subscription'] ?? 0);
$pctExBmpaFailed = $poTotalForOps > 0 ? min(100, max(0, round(($exBmpaFailed / $denOps) * 100, 1))) : 0;
$pctExRenewalMissing = $poTotalForOps > 0 ? min(100, max(0, round(($exRenewalMissing / $denOps) * 100, 1))) : 0;
@endphp
<div class="mt-10" data-role="platform-ops-risk-bars">
<div class="muted"><strong>平台健康预警Top3</strong></div>
<div class="muted muted-xs mt-6">原因型风险:补回执 / 对账 / 核对退款。</div>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-risk-no-receipt-row" href="{!! $platformOrdersQuickLinks['paid_no_receipt'] !!}" aria-label="进入无回执订单集合">
<div class="adm-mini-bar-label">无回执</div>
<div class="adm-mini-bar" data-role="ops-risk-no-receipt-bar" title="{{ $riskNoReceipt }} / {{ $poTotalForOps }}{{ $pctRiskNoReceipt }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctRiskNoReceipt }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctRiskNoReceipt }}%{{ $riskNoReceipt }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-risk-reconcile-mismatch-row" href="{!! $platformOrdersQuickLinks['reconcile_mismatch'] !!}" aria-label="进入对账不一致订单集合">
<div class="adm-mini-bar-label">对账不一致</div>
<div class="adm-mini-bar" data-role="ops-risk-reconcile-mismatch-bar" title="{{ $riskReconcileMismatch }} / {{ $poTotalForOps }}{{ $pctRiskReconcileMismatch }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctRiskReconcileMismatch }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctRiskReconcileMismatch }}%{{ $riskReconcileMismatch }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-risk-refund-inconsistent-row" href="{!! $platformOrdersQuickLinks['refund_inconsistent'] !!}" aria-label="进入退款不一致订单集合">
<div class="adm-mini-bar-label">退款不一致</div>
<div class="adm-mini-bar" data-role="ops-risk-refund-inconsistent-bar" title="{{ $riskRefundInconsistent }} / {{ $poTotalForOps }}{{ $pctRiskRefundInconsistent }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctRiskRefundInconsistent }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctRiskRefundInconsistent }}%{{ $riskRefundInconsistent }}</div>
</a>
</div>
<details class="mt-10" data-role="platform-ops-exception-bars" data-storage-key="admin.dashboard.platform_ops_exception_bars">
<summary class="muted"><strong>更多异常积压(少用)</strong></summary>
<div class="muted muted-xs mt-6">异常型治理:批量失败 / 续费缺订阅等。</div>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-exception-bmpa-failed-row" href="{!! $platformOrdersQuickLinks['bmpa_failed'] !!}" aria-label="进入BMPA失败订单集合">
<div class="adm-mini-bar-label">BMPA失败</div>
<div class="adm-mini-bar" data-role="ops-exception-bmpa-failed-bar" title="{{ $exBmpaFailed }} / {{ $poTotalForOps }}{{ $pctExBmpaFailed }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctExBmpaFailed }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctExBmpaFailed }}%{{ $exBmpaFailed }}</div>
</a>
<a class="adm-mini-bar-row adm-mini-bar-row-link mt-6" data-role="ops-exception-renewal-missing-row" href="{!! $platformOrdersQuickLinks['renewal_missing_subscription'] !!}" aria-label="进入续费缺订阅订单集合">
<div class="adm-mini-bar-label">续费缺订阅</div>
<div class="adm-mini-bar" data-role="ops-exception-renewal-missing-bar" title="{{ $exRenewalMissing }} / {{ $poTotalForOps }}{{ $pctExRenewalMissing }}%">
<span class="adm-mini-bar-fill" style="width: {{ $pctExRenewalMissing }}%"></span>
</div>
<div class="adm-mini-bar-value">{{ $pctExRenewalMissing }}%{{ $exRenewalMissing }}</div>
</a>
</details>
</div>
</div>
<div class="two-col mb-20" data-role="analysis-skeleton-row2" data-eq-col="recent-platform-orders">
<div class="card" data-role="dashboard-card-recent-platform-orders">
<div class="flex-between">
<h3 class="mt-0">最近平台订单</h3>
<a class="muted" href="{!! $platformOrdersQuickLinks['platform_orders'] !!}">查看全部</a>
</div>
<table data-role="recent-platform-orders-table">
<thead>
<tr>
<th>订单号</th>
<th>类型</th>
<th>金额</th>
<th>支付</th>
<th>状态</th>
</tr>
</thead>
<tbody>
@forelse(($recentPlatformOrders ?? []) as $po)
@php
$poShowUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/' . $po->id, $selfWithoutBack);
$hasReceiptEvidence = (data_get($po->meta, 'payment_summary.total_amount') !== null)
|| (data_get($po->meta, 'payment_receipts.0.amount') !== null);
$fixReceiptUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'add-payment-receipt');
$fixRefundReceiptUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'add-refund-receipt');
$noReceiptListUrl = $platformOrdersQuickLinks['paid_no_receipt'];
$syncErrMsg = (string) (data_get($po->meta, 'subscription_activation_error.message') ?? '');
$bmpaErrMsg = (string) (data_get($po->meta, 'batch_mark_paid_and_activate_error.message') ?? '');
$syncFailedListUrl = $platformOrdersQuickLinks['sync_failed'];
$bmpaFailedListUrl = $platformOrdersQuickLinks['bmpa_failed'];
// 扫描行:直达治理锚点(与下方提示块的链接口径保持一致)
$scanGoReconcileUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'payment-receipts');
$scanGoRefundUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'refund-receipts');
$scanGoSyncFailedUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'sync-failed');
$scanGoBmpaFailedUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'bmpa-failed');
$scanGoRelationUrl = \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'relation-subscription');
// 运营扫描用的“治理状态摘要”(不替代下方的治理提示入口,只用于快速判断)
// 注意:为避免对“未支付订单”造成误导,回执/对账/退款在非 paid/refunded 时显示 "-"。
$isPaid = ((string) $po->payment_status === 'paid');
$isRefunded = ((string) $po->payment_status === 'refunded');
$receiptStatusText = $isPaid ? ($hasReceiptEvidence ? '有' : '无') : '-';
$reconcileStatusText = ($isPaid && $hasReceiptEvidence)
? ($po->isReconcileMismatch() ? '不一致' : '一致')
: '-';
$refundStatusText = ($isPaid || $isRefunded) ? ($po->isRefundInconsistent() ? '异常' : '正常') : '-';
$syncStatusText = $syncErrMsg !== '' ? '失败' : '正常';
$bmpaStatusText = $bmpaErrMsg !== '' ? '失败' : '正常';
$subscriptionStatusText = ((string) $po->order_type === 'renewal')
? (empty($po->site_subscription_id) ? '缺' : '有')
: '-';
// 运营提效:失败原因较短时,提供“一键进入同原因集合”链接(避免复制粘贴)。
// 与平台订单列表页的阈值保持一致(避免仪表盘能点、列表页却不支持/反之)。
$FAILED_REASON_KEYWORD_MAX_LEN = (int) config('saasshop.platform_orders.sync_error_keyword_link_max_len', 200);
$syncReasonUrl = '';
if ($syncErrMsg !== '' && mb_strlen($syncErrMsg) <= $FAILED_REASON_KEYWORD_MAX_LEN) {
$syncReasonUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
'sync_status' => 'failed',
'sync_error_keyword' => $syncErrMsg,
]), $selfWithoutBack);
}
$bmpaReasonUrl = '';
if ($bmpaErrMsg !== '' && mb_strlen($bmpaErrMsg) <= $FAILED_REASON_KEYWORD_MAX_LEN) {
$bmpaReasonUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([
'bmpa_failed_only' => '1',
'bmpa_error_keyword' => $bmpaErrMsg,
]), $selfWithoutBack);
}
@endphp
<tr data-role="recent-platform-order-row" data-order-no="{{ $po->order_no }}">
<td><a class="link" href="{!! $poShowUrl !!}">{{ $po->order_no }}</a></td>
<td>
{{ $po->orderTypeLabel() }}
<div class="muted muted-xs">
{{ (string) (optional($po->merchant)->name ?: ('站点#' . (int) ($po->merchant_id ?? 0))) }}
<span class="muted"></span>
{{ (string) (optional($po->plan)->name ?: ((int) ($po->plan_id ?? 0) > 0 ? ('套餐#' . (int) $po->plan_id) : '-')) }}
</div>
<div class="muted muted-xs adm-order-scanline" data-role="recent-platform-order-scanline">
<span class="adm-order-scanitem" title="支付回执证据payment_summary/payment_receipts">
回执:
@if($receiptStatusText === '无')
<a class="link" href="{!! $fixReceiptUrl !!}"><strong>{{ $receiptStatusText }}</strong></a>
@else
<strong>{{ $receiptStatusText }}</strong>
@endif
</span>
<span class="adm-order-scansep"></span>
<span class="adm-order-scanitem" title="回执金额 vs 已付金额(仅在有回执证据时判断)">
对账:
@if($reconcileStatusText !== '-' && $reconcileStatusText !== '一致')
<a class="link" href="{!! $scanGoReconcileUrl !!}"><strong>{{ $reconcileStatusText }}</strong></a>
@else
<strong>{{ $reconcileStatusText }}</strong>
@endif
</span>
<span class="adm-order-scansep"></span>
<span class="adm-order-scanitem" title="退款金额与支付状态一致性">
退款:
@if($refundStatusText !== '-' && $refundStatusText !== '正常')
<a class="link" href="{!! $scanGoRefundUrl !!}"><strong>{{ $refundStatusText }}</strong></a>
@else
<strong>{{ $refundStatusText }}</strong>
@endif
</span>
<span class="adm-order-scansep"></span>
<span class="adm-order-scanitem" title="订阅激活同步subscription_activation_error">
同步:
@if($syncStatusText === '失败')
<a class="link" href="{!! $scanGoSyncFailedUrl !!}"><strong>{{ $syncStatusText }}</strong></a>
@else
<strong>{{ $syncStatusText }}</strong>
@endif
</span>
<span class="adm-order-scansep"></span>
<span class="adm-order-scanitem" title="批量标记已付并激活batch_mark_paid_and_activate_error">
BMPA
@if($bmpaStatusText === '失败')
<a class="link" href="{!! $scanGoBmpaFailedUrl !!}"><strong>{{ $bmpaStatusText }}</strong></a>
@else
<strong>{{ $bmpaStatusText }}</strong>
@endif
</span>
<span class="adm-order-scansep"></span>
<span class="adm-order-scanitem" title="续费单订阅关联site_subscription_id">
订阅:
@if($subscriptionStatusText === '缺')
<a class="link" href="{!! $scanGoRelationUrl !!}"><strong>{{ $subscriptionStatusText }}</strong></a>
@else
<strong>{{ $subscriptionStatusText }}</strong>
@endif
</span>
</div>
</td>
<td>¥{{ number_format((float) $po->payable_amount, 2) }}</td>
<td>
{{ $po->payment_status }}
@if((string) $po->payment_status === 'paid' && ! $hasReceiptEvidence)
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-no-receipt-hint" title="已付 ¥{{ number_format((float) $po->paid_amount, 2) }}|无回执证据">
<span class="row-warn-prefix">无回执</span>
<span class="muted"></span>
<a class="link" href="{!! $fixReceiptUrl !!}">去补回执</a>
<span class="muted"></span>
<a class="link" href="{!! $noReceiptListUrl !!}">进入集合</a>
</div>
@endif
</td>
<td>
{{ $po->status }}
@if($syncErrMsg !== '')
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-sync-failed-hint" title="{{ e($syncErrMsg) }}">
<span class="row-warn-prefix">同步失败</span>
<span class="muted"></span>
<a class="link" href="{!! $syncFailedListUrl !!}">进入集合</a>
@if($syncReasonUrl !== '')
<span class="muted"></span>
<a class="link" href="{!! $syncReasonUrl !!}">同原因集合</a>
@elseif($syncErrMsg !== '')
<span class="muted"></span>
<span class="muted muted-xs">原因过长</span>
@endif
<span class="muted"></span>
<a class="link" href="{!! $scanGoSyncFailedUrl !!}">查看失败详情</a>
</div>
@endif
@if($bmpaErrMsg !== '')
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-bmpa-failed-hint" title="{{ e($bmpaErrMsg) }}">
<span class="row-warn-prefix">BMPA失败</span>
<span class="muted"></span>
<a class="link" href="{!! $bmpaFailedListUrl !!}">进入集合</a>
@if($bmpaReasonUrl !== '')
<span class="muted"></span>
<a class="link" href="{!! $bmpaReasonUrl !!}">同原因集合</a>
@elseif($bmpaErrMsg !== '')
<span class="muted"></span>
<span class="muted muted-xs">原因过长</span>
@endif
<span class="muted"></span>
<a class="link" href="{!! $scanGoBmpaFailedUrl !!}">查看失败详情</a>
</div>
@endif
@if((string) $po->status === 'pending' && (string) $po->payment_status === 'paid' && $po->isReconcileMismatch())
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-reconcile-mismatch-hint" title="已付 ¥{{ number_format((float) $po->paid_amount, 2) }}|回执 ¥{{ number_format((float) $po->receiptTotal(), 2) }}">
<span class="row-warn-prefix">对账不一致</span>
<span class="muted"></span>
<a class="link" href="{!! $platformOrdersQuickLinks['reconcile_mismatch'] !!}">进入集合</a>
<span class="muted"></span>
<a class="link" href="{!! $scanGoReconcileUrl !!}">去对账</a>
<span class="muted"></span>
<a class="link" href="{!! $fixReceiptUrl !!}">去补回执</a>
</div>
@endif
@if($po->isRefundInconsistent())
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-refund-inconsistent-hint" title="已付 ¥{{ number_format((float) $po->paid_amount, 2) }}|退款 ¥{{ number_format((float) $po->refundTotal(), 2) }}">
<span class="row-warn-prefix">退款不一致</span>
<span class="muted"></span>
<a class="link" href="{!! $platformOrdersQuickLinks['refund_inconsistent'] !!}">进入集合</a>
<span class="muted"></span>
<a class="link" href="{!! $scanGoRefundUrl !!}">去核对退款</a>
<span class="muted"></span>
<a class="link" href="{!! $fixRefundReceiptUrl !!}">去补退款记录</a>
</div>
@endif
@if((string) $po->order_type === 'renewal' && empty($po->site_subscription_id))
<div class="muted text-danger muted-xs row-warn" data-role="recent-order-renewal-missing-subscription-hint" title="站点 #{{ (int) ($po->merchant_id ?? 0) }}|套餐 #{{ (int) ($po->plan_id ?? 0) }}site_subscription_id 为空">
<span class="row-warn-prefix">续费缺订阅</span>
<span class="muted"></span>
<a class="link" href="{!! $platformOrdersQuickLinks['renewal_missing_subscription'] !!}">进入集合</a>
<span class="muted"></span>
<a class="link" href="{!! $scanGoRelationUrl !!}">去关联订阅</a>
</div>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="5" class="muted">暂无数据</td>
</tr>
@endforelse
</tbody>
</table>
<div class="muted muted-xs mt-10">说明:当前先接入最近订单列表;后续补“同步状态/站点/套餐/治理入口”。</div>
</div>
<div class="card">
<div class="flex-between">
<h3 class="mt-0">套餐订单占比Top5</h3>
<a class="muted" href="{!! $billingEntryLinks['plans'] !!}">查看套餐</a>
</div>
<div class="muted">近7天按订单数统计</div>
@php
$shareRows = (array) ($planOrderShare ?? []);
$top5Orders = 0;
foreach ($shareRows as $r) {
$top5Orders += (int) ($r['count'] ?? 0);
}
$totalOrders = (int) ($planOrderShareTotal ?? 0);
if ($totalOrders <= 0) {
// 兜底:兼容旧数据(未传 total 时,至少不影响渲染)
$totalOrders = (int) $top5Orders;
}
@endphp
@php
// 用于前端渐进增强渲染占比条形图JS 读取 data-points
$sharePoints = [];
foreach ($shareRows as $r) {
$pid = (int) ($r['plan_id'] ?? 0);
$sharePoints[] = [
'plan_id' => $pid,
'name' => (string) (($planIdToName[$pid] ?? '') ?: ('#' . $pid)),
'count' => (int) ($r['count'] ?? 0),
];
}
@endphp
<div class="adm-mini-share" data-role="plan-order-share-top5-chart" data-total="{{ (int) $totalOrders }}" data-points='@json($sharePoints)'></div>
@php
$shareTop1Count = 0;
foreach ($shareRows as $r) {
$shareTop1Count = max($shareTop1Count, (int) ($r['count'] ?? 0));
}
$shareTop1Pct = $totalOrders > 0 ? round(($shareTop1Count / $totalOrders) * 100, 1) : 0;
$shareCoveragePct = $totalOrders > 0 ? round(($top5Orders / $totalOrders) * 100, 1) : 0;
$shareOtherPct = max(0, round(100 - $shareCoveragePct, 1));
$shareOtherCount = max(0, (int) $totalOrders - (int) $top5Orders);
@endphp
<div class="adm-mini-meta" data-role="plan-order-share-top5-meta">
<span class="adm-mini-meta-item">全量订单:<strong>{{ (int) $totalOrders }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">Top5合计<strong>{{ (int) $top5Orders }}</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">覆盖率:<strong>{{ $shareCoveragePct }}%</strong></span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">其它:<strong>{{ $shareOtherPct }}%</strong>{{ (int) $shareOtherCount }}单)</span>
<span class="adm-mini-meta-sep"></span>
<span class="adm-mini-meta-item">Top1占比<strong>{{ $shareTop1Pct }}%</strong></span>
</div>
<table data-role="plan-order-share-top5">
<thead>
<tr>
<th>套餐</th>
<th>订单数</th>
<th>占比</th>
</tr>
</thead>
<tbody>
@forelse($shareRows as $row)
@php
$planId = (int) ($row['plan_id'] ?? 0);
$count = (int) ($row['count'] ?? 0);
$pct = $totalOrders > 0 ? round(($count / $totalOrders) * 100, 1) : 0;
$planName = (string) (($planIdToName[$planId] ?? '') ?: ('#' . $planId));
@endphp
@php
$planOrdersUrl = ($platformOrdersQuickLinks['plan_orders_range'])($planId, $rangeFrom7d, $rangeTo7d);
@endphp
<tr>
<td><a class="link" href="{!! $planOrdersUrl !!}">{{ $planName }}</a></td>
<td>{{ $count }}</td>
<td>{{ $pct }}%</td>
</tr>
@empty
<tr>
<td colspan="3" class="muted">暂无数据</td>
</tr>
@endforelse
</tbody>
</table>
<div class="muted muted-xs mt-10">说明:当前口径为“平台订单按 plan_id 的数量占比Top5后续扩展到金额占比、渠道占比与时间范围切换。</div>
</div>
</div>
</div>
@endsection