HEL-157: 删除首页图表、校正主从模块并统一待审核口径
移除柱状图/折线图死代码;主从改为 480px+明细宽栏;待审核首页卡、侧栏角标与后端口径同源。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
815b68c1fc
commit
7fb97119ba
+44
-103
@@ -872,12 +872,14 @@ async function renderAdminAccountReviews() {
|
||||
(result?.accounts || []).forEach((account) => appendAdminReviewRow(account, "account"));
|
||||
updateAuditCounts();
|
||||
updatePendingAccountNotice();
|
||||
await refreshAuditCountsFromApi();
|
||||
}
|
||||
|
||||
function renderStoredAdminReviews() {
|
||||
if (!$("#auditRows")) return;
|
||||
$$('[data-stored-review]', $("#auditRows")).forEach((row) => row.remove());
|
||||
readStoredRecords(storageKeys.manual).forEach((record) => appendAdminReviewRow(record, "manual"));
|
||||
// 管理端待审列表只展示后端权威队列(当前为待复核银行账户等),
|
||||
// 不再混入 localStorage 演示手工单,避免角标/首页/列表三处口径分裂。
|
||||
renderAdminAccountReviews();
|
||||
}
|
||||
|
||||
@@ -899,26 +901,51 @@ function updateAuditCounts() {
|
||||
const badge = $(".tab-count", button);
|
||||
if (badge) badge.textContent = count;
|
||||
});
|
||||
const badge = $('.side-nav a[data-view="audit"] .nav-badge');
|
||||
if (badge) {
|
||||
badge.textContent = unresolved.length;
|
||||
badge.style.display = unresolved.length ? "" : "none";
|
||||
}
|
||||
const pending = $("#pending-count");
|
||||
if (pending) pending.textContent = unresolved.length;
|
||||
const foot = $("#auditFoot");
|
||||
if (foot) foot.textContent = `共 ${rows.length} 项 · 待审核 ${unresolved.length} 项`;
|
||||
// Keep dashboard audit card in sync with the live audit table when possible.
|
||||
// Prefer the authoritative /api/admin/dashboard payload; this is a best-effort
|
||||
// DOM fallback only when the card already exists and dashboard reload has not run.
|
||||
if ($("#dashAuditTotal") && !state.dashAuditFromApi) {
|
||||
// 角标 / 首页待审核卡 / 审核中心标题数统一走后端口径(见 applyAuditCounts)。
|
||||
// 此处仅在尚无 API 结果时,用真实列表行数作短暂回退,避免演示写死数字。
|
||||
if (!state.dashAuditFromApi) {
|
||||
const high = unresolved.filter((row) => row.querySelector(".pill-danger")).length;
|
||||
const medium = unresolved.filter((row) => row.querySelector(".pill-warn")).length;
|
||||
const low = Math.max(0, unresolved.length - high - medium);
|
||||
updateDashAuditCard({ total: unresolved.length, high, medium, low });
|
||||
applyAuditCounts({ total: unresolved.length, high, medium, low });
|
||||
}
|
||||
}
|
||||
|
||||
function applyAuditCounts(audit, { fromApi = false } = {}) {
|
||||
if (!audit) return;
|
||||
const total = Number(audit.total) || 0;
|
||||
const high = Number(audit.high) || 0;
|
||||
const medium = Number(audit.medium) || 0;
|
||||
const low = Number(audit.low) || 0;
|
||||
state.dashAudit = { total, high, medium, low };
|
||||
if (fromApi) state.dashAuditFromApi = true;
|
||||
|
||||
const totalEl = $("#dashAuditTotal");
|
||||
const footEl = $("#dashAuditFoot");
|
||||
if (totalEl) totalEl.innerHTML = `${total}<span class="unit">项</span>`;
|
||||
if (footEl) footEl.textContent = `高 ${high} 项 · 中 ${medium} 项 · 其余 ${low} 项低风险`;
|
||||
|
||||
const badge = $("#auditNavBadge") || $('.side-nav a[data-view="audit"] .nav-badge');
|
||||
if (badge) {
|
||||
badge.textContent = String(total);
|
||||
badge.style.display = total > 0 ? "" : "none";
|
||||
}
|
||||
const pending = $("#pending-count");
|
||||
if (pending) pending.textContent = String(total);
|
||||
}
|
||||
|
||||
async function refreshAuditCountsFromApi() {
|
||||
const from = await resolveDashStartDate();
|
||||
const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
|
||||
if (!response?.ok) return null;
|
||||
const data = await response.json().catch(() => null);
|
||||
if (!data || data.status !== "ok" || !data.audit) return null;
|
||||
applyAuditCounts(data.audit, { fromApi: true });
|
||||
return data.audit;
|
||||
}
|
||||
|
||||
function companyStatusBadge(status) {
|
||||
if (status === "preparing") return { className: "neutral", label: "筹备中" };
|
||||
if (status === "disabled") return { className: "danger", label: "已停用" };
|
||||
@@ -1026,91 +1053,7 @@ function statusPill(label) {
|
||||
}
|
||||
|
||||
function updateDashAuditCard(audit) {
|
||||
const totalEl = $("#dashAuditTotal");
|
||||
const footEl = $("#dashAuditFoot");
|
||||
if (!totalEl || !footEl || !audit) return;
|
||||
totalEl.innerHTML = `${audit.total ?? 0}<span class="unit">项</span>`;
|
||||
footEl.textContent = `高 ${audit.high ?? 0} 项 · 中 ${audit.medium ?? 0} 项 · 其余 ${audit.low ?? 0} 项低风险`;
|
||||
}
|
||||
|
||||
function renderBarChart(companies) {
|
||||
const host = $("#dashBarChart");
|
||||
if (!host) return;
|
||||
if (!companies.length) {
|
||||
host.innerHTML = '<div class="empty" style="padding: 36px 12px;"><div class="e-title">暂无往来分布</div><div>归集后将显示各公司期末净值</div></div>';
|
||||
return;
|
||||
}
|
||||
const values = companies.map((c) => Number(c.net_wan) || 0);
|
||||
const maxAbs = Math.max(1, ...values.map((v) => Math.abs(v)));
|
||||
const w = 560;
|
||||
const h = 220;
|
||||
const padL = 44;
|
||||
const padR = 12;
|
||||
const padT = 24;
|
||||
const padB = 36;
|
||||
const plotW = w - padL - padR;
|
||||
const plotH = h - padT - padB;
|
||||
const zeroY = padT + plotH / 2;
|
||||
const gap = 10;
|
||||
const barW = Math.max(12, (plotW - gap * companies.length) / companies.length);
|
||||
const ticks = [maxAbs, maxAbs / 2, 0, -maxAbs / 2, -maxAbs];
|
||||
const tickLines = ticks.map((t) => {
|
||||
const y = zeroY - (t / maxAbs) * (plotH / 2);
|
||||
const label = t === 0 ? "0" : (t > 0 ? `+${Math.round(t)}` : `${Math.round(t)}`);
|
||||
return `<line x1="${padL}" y1="${y}" x2="${w - padR}" y2="${y}" stroke="var(--border)" stroke-width="1"/>
|
||||
<text x="${padL - 6}" y="${y + 3}" text-anchor="end" fill="var(--muted)" font-size="10" font-family="var(--font-mono)">${label}</text>`;
|
||||
}).join("");
|
||||
const bars = companies.map((c, i) => {
|
||||
const v = Number(c.net_wan) || 0;
|
||||
const x = padL + gap / 2 + i * (barW + gap);
|
||||
const bh = (Math.abs(v) / maxAbs) * (plotH / 2);
|
||||
const y = v >= 0 ? zeroY - bh : zeroY;
|
||||
const color = v >= 0 ? "var(--success)" : "var(--danger)";
|
||||
const label = (c.name || "").replace(/河南|有限公司|科技发展/g, "").slice(0, 4) || c.name;
|
||||
const valY = v >= 0 ? y - 4 : y + bh + 11;
|
||||
return `<rect x="${x}" y="${y}" width="${barW}" height="${Math.max(bh, v === 0 ? 0 : 2)}" fill="${color}" rx="2"/>
|
||||
<text x="${x + barW / 2}" y="${valY}" text-anchor="middle" fill="${color}" font-size="10" font-family="var(--font-mono)">${formatWan(v).replace(".00", "")}</text>
|
||||
<text x="${x + barW / 2}" y="${h - 10}" text-anchor="middle" fill="var(--muted)" font-size="10">${label}</text>`;
|
||||
}).join("");
|
||||
host.innerHTML = `<svg viewBox="0 0 ${w} ${h}" width="100%" height="100%" role="img">${tickLines}<line x1="${padL}" y1="${zeroY}" x2="${w - padR}" y2="${zeroY}" stroke="var(--fg)" stroke-width="1" opacity="0.35"/>${bars}</svg>`;
|
||||
}
|
||||
|
||||
function renderLineChart(flow) {
|
||||
const host = $("#dashLineChart");
|
||||
if (!host) return;
|
||||
const labels = flow?.labels || [];
|
||||
const inflow = (flow?.inflow_wan || []).map(Number);
|
||||
const outflow = (flow?.outflow_wan || []).map(Number);
|
||||
if (!labels.length) {
|
||||
host.innerHTML = '<div class="empty" style="padding: 36px 12px;"><div class="e-title">暂无周度流水</div><div>有归集数据后显示近 7 日流入流出</div></div>';
|
||||
return;
|
||||
}
|
||||
const w = 560;
|
||||
const h = 220;
|
||||
const padL = 40;
|
||||
const padR = 12;
|
||||
const padT = 20;
|
||||
const padB = 32;
|
||||
const plotW = w - padL - padR;
|
||||
const plotH = h - padT - padB;
|
||||
const maxV = Math.max(1, ...inflow, ...outflow);
|
||||
const xAt = (i) => padL + (labels.length === 1 ? plotW / 2 : (i / (labels.length - 1)) * plotW);
|
||||
const yAt = (v) => padT + plotH - (v / maxV) * plotH;
|
||||
const pathOf = (series) => series.map((v, i) => `${i ? "L" : "M"}${xAt(i)},${yAt(v)}`).join(" ");
|
||||
const grid = [0, 0.5, 1].map((t) => {
|
||||
const y = yAt(maxV * t);
|
||||
return `<line x1="${padL}" y1="${y}" x2="${w - padR}" y2="${y}" stroke="var(--border)"/><text x="${padL - 6}" y="${y + 3}" text-anchor="end" fill="var(--muted)" font-size="10" font-family="var(--font-mono)">${Math.round(maxV * t)}</text>`;
|
||||
}).join("");
|
||||
const xLabels = labels.map((lab, i) => `<text x="${xAt(i)}" y="${h - 8}" text-anchor="middle" fill="var(--muted)" font-size="10" font-family="var(--font-mono)">${lab}</text>`).join("");
|
||||
host.innerHTML = `<svg viewBox="0 0 ${w} ${h}" width="100%" height="100%" role="img">${grid}
|
||||
<path d="${pathOf(inflow)}" fill="none" stroke="var(--success)" stroke-width="2"/>
|
||||
<path d="${pathOf(outflow)}" fill="none" stroke="var(--danger)" stroke-width="2"/>
|
||||
${inflow.map((v, i) => `<circle cx="${xAt(i)}" cy="${yAt(v)}" r="2.5" fill="var(--success)"/>`).join("")}
|
||||
${outflow.map((v, i) => `<circle cx="${xAt(i)}" cy="${yAt(v)}" r="2.5" fill="var(--danger)"/>`).join("")}
|
||||
${xLabels}
|
||||
<text x="${w - padR}" y="14" text-anchor="end" fill="var(--success)" font-size="11">流入</text>
|
||||
<text x="${w - padR - 40}" y="14" text-anchor="end" fill="var(--danger)" font-size="11">流出</text>
|
||||
</svg>`;
|
||||
applyAuditCounts(audit, { fromApi: true });
|
||||
}
|
||||
|
||||
function renderDashCompanyRows(companies, selectedId) {
|
||||
@@ -1125,10 +1068,10 @@ function renderDashCompanyRows(companies, selectedId) {
|
||||
tr.className = "clickable";
|
||||
tr.dataset.companyId = company.id;
|
||||
if (String(company.id) === String(selectedId)) {
|
||||
tr.style.background = "var(--accent-soft)";
|
||||
tr.classList.add("is-selected");
|
||||
}
|
||||
const nameTd = document.createElement("td");
|
||||
nameTd.style.cssText = "white-space: nowrap; max-width: 220px; overflow: hidden; text-overflow: ellipsis;";
|
||||
nameTd.className = "dash-company-name";
|
||||
nameTd.title = company.name;
|
||||
nameTd.textContent = company.name;
|
||||
const countTd = document.createElement("td");
|
||||
@@ -1251,13 +1194,10 @@ async function loadAdminDashboard() {
|
||||
state.dashFrom = data.from_date;
|
||||
state.dashCutoff = data.cutoff;
|
||||
updateDashAuditCard(data.audit);
|
||||
state.dashAuditFromApi = true;
|
||||
const statusHead = $("#dashStatusHead");
|
||||
if (statusHead && data.period_month) statusHead.textContent = `${data.period_month}月状态`;
|
||||
const listSub = $("#dashListSub");
|
||||
if (listSub) listSub.textContent = `${data.from_date} 至 ${data.cutoff} · 单位:万元`;
|
||||
renderBarChart(state.dashCompanies);
|
||||
renderLineChart(data.weekly_flow);
|
||||
const selected = state.dashSelectedId || state.dashCompanies[0]?.id;
|
||||
renderDashCompanyRows(state.dashCompanies, selected);
|
||||
if (selected) await loadDashCompanyDetail(selected);
|
||||
@@ -1384,6 +1324,7 @@ function initAdmin() {
|
||||
actionCell.innerHTML = `<span class="meta">${approved ? "已通过" : "已驳回"} · 系统管理员</span>`;
|
||||
}
|
||||
updateAuditCounts();
|
||||
await refreshAuditCountsFromApi();
|
||||
showToast("审核结果已记录", approved ? "已通过的数据取得相应使用或核算资格" : "当前记录不参与自动核算", "success");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user