diff --git a/public/css/admin-components.css b/public/css/admin-components.css index c7b7b78..5f1de93 100644 --- a/public/css/admin-components.css +++ b/public/css/admin-components.css @@ -415,6 +415,67 @@ color:var(--adm-text, #0f172a); } +/* 可复用:迷你占比(Top5)(渐进增强:由 admin.js 渲染,页面无 JS 时为空但不影响表格) */ +.adm-mini-share{ + margin-top:10px; + width:100%; + display:flex; + flex-direction:column; + gap:8px; + padding:10px 10px; + border:1px solid var(--adm-border-color, #e5e7eb); + border-radius:var(--adm-radius, 12px); + background:var(--adm-surface-tint, rgba(15, 23, 42, .02)); + box-shadow:var(--adm-shadow-sm); +} + +.adm-mini-share.is-empty{ + align-items:center; + justify-content:center; + color:var(--adm-text-muted, #94a3b8); + font-size:12px; +} + +.adm-mini-share-row{ + display:flex; + align-items:center; + gap:10px; +} + +.adm-mini-share-name{ + width:72px; + min-width:72px; + max-width:72px; + font-size:12px; + color:var(--adm-text-secondary, #64748b); + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; +} + +.adm-mini-share-bar-wrap{ + flex:1; + min-width:0; + height:12px; + border-radius:999px; + background:rgba(15, 23, 42, .06); + overflow:hidden; +} + +.adm-mini-share-bar{ + height:100%; + border-radius:999px; + background:linear-gradient(90deg, rgba(245, 158, 11, .45), rgba(245, 158, 11, .16)); +} + +.adm-mini-share-value{ + width:92px; + min-width:92px; + text-align:right; + font-size:12px; + color:var(--adm-text, #0f172a); +} + .kpi-grid{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); diff --git a/public/js/admin.js b/public/js/admin.js index f7df946..5dfa58e 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -195,6 +195,68 @@ }); })(); + // 仪表盘:迷你占比(套餐订单占比 Top5) + (function () { + var el = qs('[data-role="plan-order-share-top5-chart"][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 (!total || total <= 0) { + total = 0; + } + + if (!points || points.length === 0 || total === 0) { + el.classList.add('is-empty'); + el.textContent = '暂无占比数据'; + return; + } + + el.innerHTML = ''; + + points.forEach(function (p, idx) { + var cnt = Number(p && p.count ? p.count : 0); + var ratio = total > 0 ? Math.max(0, Math.min(1, cnt / total)) : 0; + + var row = document.createElement('div'); + row.className = 'adm-mini-share-row'; + + var name = document.createElement('div'); + name.className = 'adm-mini-share-name'; + name.textContent = '#' + (idx + 1); + + 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(ratio * 100) + '%'; + + wrap.appendChild(bar); + + var val = document.createElement('div'); + val.className = 'adm-mini-share-value'; + val.textContent = (ratio * 100).toFixed(1) + '%'; + + row.title = 'Top' + (idx + 1) + ':' + cnt + ' 单,占比 ' + (ratio * 100).toFixed(1) + '%'; + + row.appendChild(name); + row.appendChild(wrap); + row.appendChild(val); + + el.appendChild(row); + }); + })(); + // 通用:将后端 flash 信息同步到 toast(更像 Ant Design Pro 的反馈方式) // 说明:渐进增强。页面仍保留原本的提示块,不依赖 JS。 (function () { diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 446188d..ab62a40 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -342,6 +342,19 @@ } @endphp + @php + // 用于前端渐进增强渲染占比条形图(JS 读取 data-points) + $sharePoints = []; + foreach ($shareRows as $r) { + $sharePoints[] = [ + 'plan_id' => (int) ($r['plan_id'] ?? 0), + 'count' => (int) ($r['count'] ?? 0), + ]; + } + @endphp + +
+