627 lines
36 KiB
PHP
627 lines
36 KiB
PHP
@extends('admin.layouts.app')
|
||
|
||
@section('title', '总台仪表盘')
|
||
@section('page_title', '总台仪表盘')
|
||
|
||
@section('content')
|
||
@php
|
||
$incomingBack = (string) request()->query('back', '');
|
||
$safeBackForLinks = \App\Support\BackUrl::sanitizeForLinks($incomingBack);
|
||
|
||
// 用于构建“从仪表盘跳到其它治理页后可返回仪表盘”的 back
|
||
$selfWithoutBack = \App\Support\BackUrl::selfWithoutBack();
|
||
|
||
$billingEntryLinks = [
|
||
'platform_orders' => \App\Support\BackUrl::withBack('/admin/platform-orders', $safeBackForLinks),
|
||
'site_subscriptions' => \App\Support\BackUrl::withBack('/admin/site-subscriptions', $safeBackForLinks),
|
||
'plans' => \App\Support\BackUrl::withBack('/admin/plans', $safeBackForLinks),
|
||
];
|
||
|
||
$platformOrdersQuickLinks = [
|
||
'unpaid_pending' => \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=unpaid&status=pending', $safeBackForLinks),
|
||
// 待生效:paid + pending,并显式锁定 sync_status=unsynced(排除同步失败等异常单)
|
||
'paid_pending' => \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=paid&status=pending&sync_status=unsynced', $safeBackForLinks),
|
||
'syncable_only' => \App\Support\BackUrl::withBack('/admin/platform-orders?syncable_only=1', $safeBackForLinks),
|
||
'sync_failed' => \App\Support\BackUrl::withBack('/admin/platform-orders?sync_status=failed', $safeBackForLinks),
|
||
];
|
||
@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' => \App\Support\BackUrl::withBack('/admin/plans', $selfWithoutBack),
|
||
'site_subscriptions' => \App\Support\BackUrl::withBack('/admin/site-subscriptions', $selfWithoutBack),
|
||
'platform_orders' => \App\Support\BackUrl::withBack('/admin/platform-orders', $selfWithoutBack),
|
||
];
|
||
@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">
|
||
<div class="card">
|
||
<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 = \App\Support\BackUrl::withBack(
|
||
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||
'created_from' => $d,
|
||
'created_to' => $d,
|
||
]),
|
||
$selfWithoutBack
|
||
);
|
||
@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);
|
||
}
|
||
@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">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 = \App\Support\BackUrl::withBack(
|
||
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||
'merchant_id' => $mid,
|
||
'created_from' => now()->subDays(6)->format('Y-m-d'),
|
||
'created_to' => now()->format('Y-m-d'),
|
||
]),
|
||
$selfWithoutBack
|
||
);
|
||
@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">
|
||
<div class="card" id="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">
|
||
<a class="btn btn-secondary btn-sm" href="{!! $platformOrdersQuickLinks['unpaid_pending'] !!}">待支付({{ (int) ($stats['platform_orders_unpaid_pending'] ?? 0) }})</a>
|
||
<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['syncable_only'] !!}">可同步({{ (int) ($stats['platform_orders_syncable'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! $platformOrdersQuickLinks['sync_failed'] !!}">同步失败({{ (int) ($stats['platform_orders_sync_failed'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/platform-orders?renewal_missing_subscription=1', $selfWithoutBack) !!}">续费缺订阅({{ (int) ($stats['platform_orders_renewal_missing_subscription'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/platform-orders?bmpa_failed_only=1', $selfWithoutBack) !!}">BMPA失败({{ (int) ($stats['platform_orders_bmpa_failed'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=paid&receipt_status=none', $selfWithoutBack) !!}">无回执({{ (int) ($stats['platform_orders_paid_no_receipt'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/platform-orders?reconcile_mismatch=1', $selfWithoutBack) !!}">对账不一致({{ (int) ($stats['platform_orders_reconcile_mismatch'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/platform-orders?refund_inconsistent=1', $selfWithoutBack) !!}">退款不一致({{ (int) ($stats['platform_orders_refund_inconsistent'] ?? 0) }})</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-12">
|
||
<div class="muted">订阅到期治理:</div>
|
||
<div class="actions mt-8">
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/site-subscriptions?expiry=expiring_7d', $selfWithoutBack) !!}">7天内到期({{ (int) ($stats['site_subscriptions_expiring_7d'] ?? 0) }})</a>
|
||
<a class="btn btn-secondary btn-sm" href="{!! \App\Support\BackUrl::withBack('/admin/site-subscriptions?expiry=expired', $selfWithoutBack) !!}">已过期({{ (int) ($stats['site_subscriptions_expired'] ?? 0) }})</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="muted muted-xs mt-10">说明:这里先把收费主链的高频治理入口收敛到仪表盘;后续再补趋势/排行的真实聚合。</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 class="mt-0">平台定位</h3>
|
||
<table>
|
||
<tr><th>后台角色</th><td>{{ $platformOverview['system_role'] }}</td></tr>
|
||
<tr><th>当前视角</th><td>{{ $platformOverview['current_scope'] }}</td></tr>
|
||
<tr><th>商家模式</th><td>{{ $platformOverview['merchant_mode'] }}</td></tr>
|
||
<tr><th>渠道数</th><td>{{ $platformOverview['channel_count'] }}</td></tr>
|
||
<tr><th>活跃商家</th><td>{{ $platformOverview['active_merchants'] }}</td></tr>
|
||
<tr><th>待处理订单</th><td>{{ $platformOverview['pending_orders'] }}</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="two-col mb-20" data-role="analysis-skeleton-row2">
|
||
<div class="card">
|
||
<div class="flex-between">
|
||
<h3 class="mt-0">最近平台订单</h3>
|
||
@php
|
||
$platformOrdersIndexUrl = \App\Support\BackUrl::withBack('/admin/platform-orders', $selfWithoutBack);
|
||
@endphp
|
||
<a class="muted" href="{!! $platformOrdersIndexUrl !!}">查看全部</a>
|
||
</div>
|
||
|
||
<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');
|
||
$noReceiptListUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?payment_status=paid&receipt_status=none', $selfWithoutBack);
|
||
|
||
$syncErrMsg = (string) (data_get($po->meta, 'subscription_activation_error.message') ?? '');
|
||
$bmpaErrMsg = (string) (data_get($po->meta, 'batch_mark_paid_and_activate_error.message') ?? '');
|
||
|
||
$syncFailedListUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?sync_status=failed', $selfWithoutBack);
|
||
$bmpaFailedListUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?bmpa_failed_only=1', $selfWithoutBack);
|
||
|
||
// 扫描行:直达治理锚点(与下方提示块的链接口径保持一致)
|
||
$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 = 80;
|
||
$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)">回执:<strong>{{ $receiptStatusText }}</strong></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="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'sync-failed') !!}">查看失败详情</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="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'bmpa-failed') !!}">查看失败详情</a>
|
||
</div>
|
||
@endif
|
||
@if((string) $po->status === 'pending' && (string) $po->payment_status === 'paid' && $po->isReconcileMismatch())
|
||
@php
|
||
$reconcileMismatchUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?reconcile_mismatch=1', $selfWithoutBack);
|
||
@endphp
|
||
<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="{!! $reconcileMismatchUrl !!}">进入集合</a>
|
||
<span class="muted">|</span>
|
||
<a class="link" href="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'payment-receipts') !!}">去对账</a>
|
||
<span class="muted">|</span>
|
||
<a class="link" href="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'add-payment-receipt') !!}">去补回执</a>
|
||
</div>
|
||
@endif
|
||
@if($po->isRefundInconsistent())
|
||
@php
|
||
$refundInconsistentUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?refund_inconsistent=1', $selfWithoutBack);
|
||
@endphp
|
||
<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="{!! $refundInconsistentUrl !!}">进入集合</a>
|
||
<span class="muted">|</span>
|
||
<a class="link" href="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'refund-receipts') !!}">去核对退款</a>
|
||
<span class="muted">|</span>
|
||
<a class="link" href="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'add-refund-receipt') !!}">去补退款记录</a>
|
||
</div>
|
||
@endif
|
||
@if((string) $po->order_type === 'renewal' && empty($po->site_subscription_id))
|
||
@php
|
||
$renewalMissingSubscriptionUrl = \App\Support\BackUrl::withBack('/admin/platform-orders?renewal_missing_subscription=1', $selfWithoutBack);
|
||
@endphp
|
||
<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="{!! $renewalMissingSubscriptionUrl !!}">进入集合</a>
|
||
<span class="muted">|</span>
|
||
<a class="link" href="{!! \App\Support\BackUrl::withBackAndFragment('/admin/platform-orders/' . $po->id, $selfWithoutBack, 'relation-subscription') !!}">去关联订阅</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>
|
||
@php
|
||
$plansIndexUrl = \App\Support\BackUrl::withBack('/admin/plans', $selfWithoutBack);
|
||
@endphp
|
||
<a class="muted" href="{!! $plansIndexUrl !!}">查看套餐</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>
|
||
<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 = \App\Support\BackUrl::withBack(
|
||
'/admin/platform-orders?' . \Illuminate\Support\Arr::query([
|
||
'plan_id' => $planId,
|
||
'created_from' => now()->subDays(6)->format('Y-m-d'),
|
||
'created_to' => now()->format('Y-m-d'),
|
||
]),
|
||
$selfWithoutBack
|
||
);
|
||
@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
|