diff --git a/public/js/admin.js b/public/js/admin.js index 4091927..2e25876 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -334,6 +334,109 @@ }); })(); + + + // 仪表盘:最近平台订单(概览 mini bars) + // 说明:让“最近订单列表”也纳入数据图形化体系,但保留明细扫描行。 + // 渐进增强:由 Blade 输出 data-points(label/count/href/tone),JS 渲染为可点击条目。 + (function(){ + var el = qs('[data-role="recent-platform-orders-mini-bars"][data-points]'); + if (!el) { + return; + } + + var raw = el.getAttribute('data-points') || '[]'; + var points = []; + try { + points = JSON.parse(raw) || []; + } catch (e) { + points = []; + } + + var total = Number(el.getAttribute('data-total') || 0); + if (!isFinite(total) || total < 0) { + total = 0; + } + + if (!points || points.length === 0) { + el.classList.add('is-empty'); + el.textContent = '暂无最近订单概览'; + return; + } + + // 视觉口径:按当前 points 内最大值归一(更易读);tooltip 同时提供 count/占比。 + var max = 0; + points.forEach(function(p){ + var c = Number(p && p.count != null ? p.count : 0); + if (isFinite(c) && c > max) { + max = c; + } + }); + if (!max || max <= 0) { + max = 1; + } + + function toneToGradient(tone){ + if (tone === 'danger') { + return 'linear-gradient(90deg, rgba(239, 68, 68, .45), rgba(239, 68, 68, .14))'; + } + if (tone === 'warn') { + return 'linear-gradient(90deg, rgba(245, 158, 11, .45), rgba(245, 158, 11, .16))'; + } + if (tone === 'success') { + return 'linear-gradient(90deg, rgba(34, 197, 94, .40), rgba(34, 197, 94, .12))'; + } + return 'linear-gradient(90deg, rgba(22, 119, 255, .35), rgba(22, 119, 255, .12))'; + } + + el.innerHTML = ''; + + points.forEach(function(p, idx){ + var label = (p && p.label) ? String(p.label) : ('#' + String(idx + 1)); + var cnt = Number(p && p.count != null ? p.count : 0); + if (!isFinite(cnt) || cnt < 0) { + cnt = 0; + } + + var href = (p && p.href) ? String(p.href) : ''; + var tone = (p && p.tone) ? String(p.tone) : ''; + + var row = document.createElement(href ? 'a' : 'div'); + row.className = 'adm-mini-share-row' + (href ? ' adm-mini-share-row-link' : ''); + if (href) { + row.setAttribute('href', href); + row.setAttribute('role', 'link'); + row.setAttribute('aria-label', '进入最近订单集合:' + label); + } + + var name = document.createElement('div'); + name.className = 'adm-mini-share-name'; + name.textContent = label; + name.title = label; + + var wrap = document.createElement('div'); + wrap.className = 'adm-mini-share-bar-wrap'; + + var bar = document.createElement('div'); + bar.className = 'adm-mini-share-bar'; + bar.style.width = Math.round(Math.max(0, Math.min(1, cnt / max)) * 100) + '%'; + bar.style.background = toneToGradient(tone); + wrap.appendChild(bar); + + var val = document.createElement('div'); + val.className = 'adm-mini-share-value'; + val.textContent = String(cnt); + + var pct = (total > 0) ? (cnt / total) : 0; + row.title = label + '|' + String(cnt) + ' 单|占比 ' + formatPct(pct, 1) + '%'; + + row.appendChild(name); + row.appendChild(wrap); + row.appendChild(val); + + el.appendChild(row); + }); + })(); // 仪表盘:迷你占比(套餐订单占比 Top5) (function () { var el = qs('[data-role="plan-order-share-top5-chart"][data-points]'); diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index fa9ca35..9103160 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -825,6 +825,117 @@ 查看全部 + + @php + // 最近平台订单:概览 mini chart(渐进增强) + // 目标:让“最近订单列表”也纳入数据图形化体系,但不牺牲明细扫描行。 + $recentPos = (array) ($recentPlatformOrders ?? []); + $recentTotal = count($recentPos); + + $recentCntBmpaSuccess = 0; + $recentCntBmpaFailed = 0; + $recentCntSyncSuccess = 0; + $recentCntSyncFailed = 0; + $recentCntRenewalMissing = 0; + $recentCntRefundTrace = 0; + $recentCntReconcileMismatch = 0; + + foreach ($recentPos as $rpo) { + $orderType = (string) ($rpo->order_type ?? ''); + + $bmpaErr = (string) (data_get($rpo, 'meta.batch_mark_paid_and_activate_error.message') ?? ''); + $bmpaRunId = (string) (data_get($rpo, 'meta.batch_mark_paid_and_activate.run_id') ?? ''); + if ($bmpaErr !== '') { + $recentCntBmpaFailed++; + } elseif ($bmpaRunId !== '') { + $recentCntBmpaSuccess++; + } + + $syncErr = (string) (data_get($rpo, 'meta.subscription_activation_error.message') ?? ''); + $syncSubId = (string) (data_get($rpo, 'meta.subscription_activation.subscription_id') ?? ''); + if ($syncErr !== '') { + $recentCntSyncFailed++; + } elseif ($syncSubId !== '') { + $recentCntSyncSuccess++; + } + + if ($orderType === 'renewal' && ((int) ($rpo->site_subscription_id ?? 0) <= 0)) { + $recentCntRenewalMissing++; + } + + $refundTotal = (float) (data_get($rpo, 'refund_total') ?? 0); + if ($refundTotal > 0) { + $recentCntRefundTrace++; + } + + if ((bool) (data_get($rpo, 'reconciliation_delta_row') ?? 0) != 0) { + $recentCntReconcileMismatch++; + } + } + + // clickable links: 复用 $platformOrdersQuickLinks(保证口径不漂移) + $recentMiniPoints = [ + [ + 'key' => 'bmpa_success', + 'label' => 'BMPA成功', + 'count' => $recentCntBmpaSuccess, + 'href' => $platformOrdersQuickLinks['bmpa_success'] ?? $platformOrdersQuickLinks['platform_orders'], + 'tone' => 'success', + ], + [ + 'key' => 'bmpa_failed', + 'label' => 'BMPA失败', + 'count' => $recentCntBmpaFailed, + 'href' => $platformOrdersQuickLinks['bmpa_failed'] ?? $platformOrdersQuickLinks['platform_orders'], + 'tone' => 'danger', + ], + [ + 'key' => 'sync_success', + 'label' => '同步成功', + 'count' => $recentCntSyncSuccess, + 'href' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ + 'sync_status' => 'synced', + ]), $selfWithoutBack), + 'tone' => 'success', + ], + [ + 'key' => 'sync_failed', + 'label' => '同步失败', + 'count' => $recentCntSyncFailed, + 'href' => $platformOrdersQuickLinks['sync_failed'] ?? $platformOrdersQuickLinks['platform_orders'], + 'tone' => 'danger', + ], + [ + 'key' => 'renewal_missing_subscription', + 'label' => '续费缺订阅', + 'count' => $recentCntRenewalMissing, + 'href' => $platformOrdersQuickLinks['renewal_missing_subscription'] ?? $platformOrdersQuickLinks['platform_orders'], + 'tone' => 'warn', + ], + [ + 'key' => 'refund_trace', + 'label' => '有退款轨迹', + 'count' => $recentCntRefundTrace, + 'href' => \App\Support\BackUrl::withBack('/admin/platform-orders?refund_status=has', $selfWithoutBack), + 'tone' => 'warn', + ], + [ + 'key' => 'reconcile_mismatch', + 'label' => '对账不一致', + 'count' => $recentCntReconcileMismatch, + 'href' => $platformOrdersQuickLinks['reconcile_mismatch'] ?? $platformOrdersQuickLinks['platform_orders'], + 'tone' => 'danger', + ], + ]; + @endphp + +
+ +