HEL-342: 管理总览 KPI 绑定真实 dashboard 接口

去掉写死的演示金额与账期进度,改由 /api/admin/dashboard 的 totals 与 period_progress 填充;无数据走空态。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-09-01 19:32:59 +08:00
co-authored by Cursor multica-agent
parent b905cba52c
commit b2bcd47e62
12 changed files with 285 additions and 34 deletions
+16 -16
View File
@@ -11,7 +11,7 @@
document.documentElement.classList.add("v-fusion");
})();
</script>
<link rel="stylesheet" href="design-system.css?v=11" />
<link rel="stylesheet" href="design-system.css?v=12" />
</head>
<body class="is-app" data-portal="admin">
<a class="skip-link" href="#main-content">跳到主要内容</a>
@@ -62,8 +62,8 @@
<div class="topbar">
<span class="crumb">管理端 / <b id="currentViewName">管理总览</b></span>
<div class="topbar-right">
<span class="tag">账期 2026-07</span>
<span class="tag">统计截止 2026-08-20</span>
<span class="tag" id="topbarPeriod">账期 —</span>
<span class="tag" id="topbarCutoff">统计截止 —</span>
<button class="btn btn-sm" data-view-link="settings">结账检查</button>
</div>
</div>
@@ -73,20 +73,20 @@
<div class="page-head">
<div>
<h1>管理总览</h1>
<p class="page-sub">2026-01-01 至 2026-08-20,集团 6 家成员公司相互往来累计发生 8.11 亿元、392 笔明细。页面上任意数字与公司均可逐级穿透,直至银行原始流水。</p>
<p class="page-sub" id="dashPageSub">正在读取集团往来汇总…</p>
</div>
</div>
<div class="grid grid-4">
<div class="card stat-card gold">
<div class="stat-label">往来借方总额 · 年初至今</div>
<div class="stat-value">42,040.30<span class="unit">万元</span></div>
<div class="stat-foot">6 家公司合计 · 明细 392 笔</div>
<div class="stat-value" id="dashDebitValue"><span class="unit">万元</span></div>
<div class="stat-foot" id="dashDebitFoot">加载中…</div>
</div>
<div class="card stat-card gold">
<div class="stat-label">往来贷方总额 · 年初至今</div>
<div class="stat-value">39,040.30<span class="unit">万元</span></div>
<div class="stat-foot">6 家公司合计 · 与借方同源互证</div>
<div class="stat-value" id="dashCreditValue"><span class="unit">万元</span></div>
<div class="stat-foot" id="dashCreditFoot">加载中…</div>
</div>
<div class="card stat-card warn" data-view-link="audit" style="cursor: pointer;">
<div class="stat-label">待审核事项 · 审核中心</div>
@@ -94,10 +94,10 @@
<div class="stat-foot" id="dashAuditFoot">高 0 项 · 中 0 项 · 其余 0 项低风险</div>
</div>
<div class="card stat-card">
<div class="stat-label">7 月账期确认进度</div>
<div class="stat-value">50<span class="unit">%</span></div>
<div class="progress" style="margin-top: 10px;"><span style="width: 50%;"></span></div>
<div class="stat-foot" style="margin-top: 8px;">3 家已完成 · 2 家在途 · 1 家未提交</div>
<div class="stat-label" id="dashPeriodLabel">账期确认进度</div>
<div class="stat-value" id="dashPeriodPercent"><span class="unit">%</span></div>
<div class="progress" style="margin-top: 10px;"><span id="dashPeriodBar" style="width: 0%;"></span></div>
<div class="stat-foot" id="dashPeriodFoot" style="margin-top: 8px;">加载中…</div>
</div>
</div>
@@ -428,11 +428,11 @@
</div>
<div class="field">
<label for="flowStart">日期起</label>
<input class="input" id="flowStart" type="date" value="2026-07-01" />
<input class="input" id="flowStart" type="date" />
</div>
<div class="field">
<label for="flowEnd">日期止</label>
<input class="input" id="flowEnd" type="date" value="2026-07-31" />
<input class="input" id="flowEnd" type="date" />
</div>
<div class="field" style="min-width: 200px;">
<label for="flowKeyword">关键词</label>
@@ -766,7 +766,7 @@
<div class="card-head">
<span class="card-title">待提醒清单<span class="sub">系统按流水提交、断档、待确认自动发现,点发送即送达对应公司</span></span>
<div class="row" style="gap: 10px; align-items: center;">
<label class="row" style="gap:6px;flex:none;"><input type="checkbox" class="ds-check" id="pending-check-all" data-check-all aria-label="全选待提醒" /></label>
<label class="row" style="gap:6px;flex:none;"><input type="checkbox" class="ds-check" id="pending-check-all" data-check-all aria-label="全选待提醒" disabled /></label>
<span class="meta" id="pending-summary"></span>
<button type="button" class="btn btn-primary btn-sm" id="pending-send-all" disabled>全部一键发送</button>
</div>
@@ -1228,6 +1228,6 @@
</div>
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
<script src="theme.js?v=1"></script>
<script src="app.js?v=16"></script>
<script src="app.js?v=17"></script>
</body>
</html>
+67
View File
@@ -1467,6 +1467,63 @@ function updateDashAuditCard(audit) {
applyAuditCounts(audit, { fromApi: true });
}
function formatDashWan(value) {
const num = Number(value);
if (!Number.isFinite(num)) return "0.00";
return Math.abs(num).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function applyDashKpis(data) {
const totals = data?.totals || {};
const from = data?.from_date || "—";
const cutoff = data?.cutoff || "—";
const periodLabel = data?.period_label || "";
const companyCount = Number(totals.company_count) || 0;
const detailCount = Number(totals.detail_count) || 0;
const debit = formatDashWan(totals.debit_wan);
const credit = formatDashWan(totals.credit_wan);
const pageSub = $("#dashPageSub");
if (pageSub) {
if (!companyCount && !detailCount) {
pageSub.textContent = `${from}${cutoff},暂无成员公司往来数据。导入并归集银行流水后,金额将显示在此。页面上任意数字与公司均可逐级穿透,直至银行原始流水。`;
} else {
pageSub.textContent = `${from}${cutoff},集团 ${companyCount} 家成员公司相互往来累计发生 ${debit} 万元、${detailCount} 笔明细。页面上任意数字与公司均可逐级穿透,直至银行原始流水。`;
}
}
const debitVal = $("#dashDebitValue");
if (debitVal) debitVal.innerHTML = `${debit}<span class="unit">万元</span>`;
const debitFoot = $("#dashDebitFoot");
if (debitFoot) debitFoot.textContent = `${companyCount} 家公司合计 · 明细 ${detailCount}`;
const creditVal = $("#dashCreditValue");
if (creditVal) creditVal.innerHTML = `${credit}<span class="unit">万元</span>`;
const creditFoot = $("#dashCreditFoot");
if (creditFoot) creditFoot.textContent = `${companyCount} 家公司合计 · 与借方同源互证`;
const progress = data?.period_progress || {};
const ym = progress.year_month || periodLabel;
const monthNum = ym ? Number(String(ym).slice(5, 7)) : 0;
const labelEl = $("#dashPeriodLabel");
if (labelEl) labelEl.textContent = monthNum ? `${monthNum} 月账期确认进度` : "账期确认进度";
const percent = Number(progress.percent) || 0;
const pctEl = $("#dashPeriodPercent");
if (pctEl) pctEl.innerHTML = `${percent}<span class="unit">%</span>`;
const bar = $("#dashPeriodBar");
if (bar) bar.style.width = `${Math.max(0, Math.min(100, percent))}%`;
const periodFoot = $("#dashPeriodFoot");
if (periodFoot) {
const enabled = Number(progress.enabled_count) || 0;
periodFoot.textContent = enabled
? `${progress.done || 0} 家已完成 · ${progress.in_progress || 0} 家在途 · ${progress.unsubmitted || 0} 家未提交`
: "尚无已启用银行账户";
}
const tbPeriod = $("#topbarPeriod");
if (tbPeriod) tbPeriod.textContent = periodLabel ? `账期 ${periodLabel}` : "账期 —";
const tbCutoff = $("#topbarCutoff");
if (tbCutoff) tbCutoff.textContent = cutoff && cutoff !== "—" ? `统计截止 ${cutoff}` : "统计截止 —";
}
function renderDashCompanyRows(companies, selectedId) {
const list = $("#dashCompanyRows");
if (!list) return;
@@ -1681,11 +1738,15 @@ async function loadAdminDashboard() {
const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
if (!response?.ok) {
$("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
const pageSub = $("#dashPageSub");
if (pageSub) pageSub.textContent = "总览加载失败,请刷新后重试。";
return;
}
const data = await response.json().catch(() => null);
if (!data || data.status !== "ok") {
$("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
const pageSub = $("#dashPageSub");
if (pageSub) pageSub.textContent = "总览加载失败,请刷新后重试。";
return;
}
state.dashCompanies = data.companies || [];
@@ -1693,6 +1754,7 @@ async function loadAdminDashboard() {
state.dashCutoff = data.cutoff;
state.dashPeriodMonth = data.period_month;
updateDashAuditCard(data.audit);
applyDashKpis(data);
const listSub = $("#dashListSub");
if (listSub) {
listSub.textContent = `${data.from_date}${data.cutoff} · 单位:万元 · 左侧选择公司,右侧查看其往来明细`;
@@ -1837,8 +1899,13 @@ function syncPendingCheckAll() {
if (!master) return;
const boxes = $$(".pending-check");
const checked = boxes.filter((el) => el.checked).length;
master.disabled = boxes.length === 0;
master.checked = boxes.length > 0 && checked === boxes.length;
master.indeterminate = checked > 0 && checked < boxes.length;
if (boxes.length === 0) {
master.checked = false;
master.indeterminate = false;
}
}
async function loadAdminRemindersHistory(sourceFilter) {
+6 -6
View File
@@ -11,7 +11,7 @@
document.documentElement.classList.add("v-fusion");
})();
</script>
<link rel="stylesheet" href="design-system.css?v=11" />
<link rel="stylesheet" href="design-system.css?v=12" />
</head>
<body class="is-app" data-portal="company">
<a class="skip-link" href="#main-content">跳到主要内容</a>
@@ -62,8 +62,8 @@
<div class="topbar">
<span class="crumb">公司业务端 / <b id="currentViewName">工作台</b></span>
<div class="topbar-right">
<span class="tag">账期 2026-07</span>
<span class="tag">统计截止 2026-08-20</span>
<span class="tag" id="topbarPeriod">账期 —</span>
<span class="tag" id="topbarCutoff">统计截止 —</span>
</div>
</div>
@@ -415,11 +415,11 @@
</div>
<div class="field">
<label for="flowStart">日期起</label>
<input class="input num-input" id="flowStart" type="date" value="2026-07-01" />
<input class="input num-input" id="flowStart" type="date" />
</div>
<div class="field">
<label for="flowEnd">日期止</label>
<input class="input num-input" id="flowEnd" type="date" value="2026-08-20" />
<input class="input num-input" id="flowEnd" type="date" />
</div>
<div class="field" style="min-width: 200px;">
<label for="flowKeyword">关键词</label>
@@ -1044,6 +1044,6 @@
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
<script src="theme.js?v=1"></script>
<script src="app.js?v=16"></script>
<script src="app.js?v=17"></script>
</body>
</html>
+1 -1
View File
@@ -1111,7 +1111,7 @@ a.flow-step.doing:hover { background: color-mix(in oklch, var(--warn) 16%, trans
/* ─── 入口页(index) ────────────────────────────────────────────── */
.portal-wrap { min-height: 100vh; display: grid; place-items: center; padding: 40px 24px; }
.portal-inner { width: 100%; max-width: 880px; }
.portal-inner { width: 100%; max-width: 880px; position: relative; }
.portal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 28px; }
.portal-card {
display: block;
+2 -2
View File
@@ -10,12 +10,12 @@
document.documentElement.classList.add("v-fusion");
})();
</script>
<link rel="stylesheet" href="design-system.css?v=11" />
<link rel="stylesheet" href="design-system.css?v=12" />
</head>
<body class="login-page">
<div class="portal-wrap">
<div class="portal-inner">
<div class="login-theme" style="position:absolute;top:18px;right:18px;width:148px;">
<div class="login-theme">
<div class="theme-seg" role="group" aria-label="主题">
<button type="button" data-theme-set="day" class="is-active" aria-pressed="true">日间</button>
<button type="button" data-theme-set="night" aria-pressed="false">夜间</button>
+1 -1
View File
@@ -10,7 +10,7 @@
document.documentElement.classList.add("v-fusion");
})();
</script>
<link rel="stylesheet" href="design-system.css?v=11" />
<link rel="stylesheet" href="design-system.css?v=12" />
</head>
<body class="login-page" data-role="admin">
<div class="login-wrap">
+1 -1
View File
@@ -10,7 +10,7 @@
document.documentElement.classList.add("v-fusion");
})();
</script>
<link rel="stylesheet" href="design-system.css?v=11" />
<link rel="stylesheet" href="design-system.css?v=12" />
</head>
<body class="login-page" data-role="company">
<div class="login-wrap">