@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
@php
$kpiLinks = [
'merchants' => \App\Support\BackUrl::withBack('/admin/merchants', $selfWithoutBack),
'plans' => $billingEntryLinks['plans'],
'site_subscriptions' => $billingEntryLinks['site_subscriptions'],
'platform_orders' => $billingEntryLinks['platform_orders'],
];
@endphp
趋势
近7天|平台订单(按天)
@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
@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
7天合计已付:¥{{ number_format($trendPaidTotal, 2) }}
|
7天订单数:{{ (int) $trendOrdersTotal }}
|
峰值:¥{{ number_format($trendPaidMax, 2) }}
| 日期 |
订单数 |
已付金额 |
@forelse($trendRows as $row)
@php
$d = (string) ($row['date'] ?? '');
$dayOrdersUrl = ($platformOrdersQuickLinks['platform_orders_range'])($d, $d);
@endphp
| {{ $d }} |
{{ (int) ($row['count'] ?? 0) }} |
¥{{ number_format((float) ($row['paid_sum'] ?? 0), 2) }} |
@empty
| 暂无数据 |
@endforelse
说明:先接入最小可用趋势数据;后续再补时间范围切换、维度切换与可视化图表。
排行(近7天站点收入 Top5)
@php
$rankRows = (array) ($merchantRevenueRank7d ?? []);
$rankTotal = 0.0;
foreach ($rankRows as $r) {
$rankTotal += (float) ($r['paid_sum'] ?? 0);
}
@endphp
近7天|按已付金额统计
@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
@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
Top5合计已付:¥{{ number_format($rankTotal, 2) }}
|
Top5订单数:{{ (int) $rankOrdersTotal }}
|
覆盖率:{{ $rankCoveragePct }}%
|
其它:{{ $rankOtherPct }}%(¥{{ number_format($rankOtherPaid, 2) }})
|
Top1金额:¥{{ number_format($rankPaidMax, 2) }}
| 站点 |
订单数 |
已付金额 |
@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
| {{ $mname }} |
{{ (int) ($row['count'] ?? 0) }} |
¥{{ number_format((float) ($row['paid_sum'] ?? 0), 2) }} |
@empty
| 暂无数据 |
@endforelse
说明:先落最小可用 Top5 排行;后续补时间范围切换、维度切换与异常排行。
收费工作台(快捷治理)
聚焦收费闭环的日常治理入口:订单 → 订阅 → 套餐。
平台订单快捷筛选(只保留“点完能做事”的治理入口):
高级筛选(少用,默认收起)
@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
订阅到期治理:
@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
说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。
平台定位(运营版)
只保留“看完知道下一步做什么”的北极星指标与治理积压。
@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
@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
@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
更多异常积压(少用)
异常型治理:批量失败 / 续费缺订阅等。
BMPA失败
{{ $pctExBmpaFailed }}%({{ $exBmpaFailed }})
续费缺订阅
{{ $pctExRenewalMissing }}%({{ $exRenewalMissing }})
| 订单号 |
类型 |
金额 |
支付 |
状态 |
@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
| {{ $po->order_no }} |
{{ $po->orderTypeLabel() }}
{{ (string) (optional($po->merchant)->name ?: ('站点#' . (int) ($po->merchant_id ?? 0))) }}
|
{{ (string) (optional($po->plan)->name ?: ((int) ($po->plan_id ?? 0) > 0 ? ('套餐#' . (int) $po->plan_id) : '-')) }}
|
¥{{ number_format((float) $po->payable_amount, 2) }} |
{{ $po->payment_status }}
@if((string) $po->payment_status === 'paid' && ! $hasReceiptEvidence)
@endif
|
{{ $po->status }}
@if($syncErrMsg !== '')
同步失败
|
进入集合
@if($syncReasonUrl !== '')
|
同原因集合
@elseif($syncErrMsg !== '')
|
原因过长
@endif
|
查看失败详情
@endif
@if($bmpaErrMsg !== '')
BMPA失败
|
进入集合
@if($bmpaReasonUrl !== '')
|
同原因集合
@elseif($bmpaErrMsg !== '')
|
原因过长
@endif
|
查看失败详情
@endif
@if((string) $po->status === 'pending' && (string) $po->payment_status === 'paid' && $po->isReconcileMismatch())
对账不一致
|
进入集合
|
去对账
|
去补回执
@endif
@if($po->isRefundInconsistent())
@endif
@if((string) $po->order_type === 'renewal' && empty($po->site_subscription_id))
@endif
|
@empty
| 暂无数据 |
@endforelse
说明:当前先接入最近订单列表;后续补“同步状态/站点/套餐/治理入口”。
近7天|按订单数统计
@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
@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
全量订单:{{ (int) $totalOrders }}
|
Top5合计:{{ (int) $top5Orders }}
|
覆盖率:{{ $shareCoveragePct }}%
|
其它:{{ $shareOtherPct }}%({{ (int) $shareOtherCount }}单)
|
Top1占比:{{ $shareTop1Pct }}%
| 套餐 |
订单数 |
占比 |
@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
| {{ $planName }} |
{{ $count }} |
{{ $pct }}% |
@empty
| 暂无数据 |
@endforelse
说明:当前口径为“平台订单按 plan_id 的数量占比(Top5)”;后续扩展到金额占比、渠道占比与时间范围切换。
@endsection