HEL-162: 按方案A重做首页一体卡片主从区域
合并公司往来合计/明细为一张共享头卡片;左侧改列表行选中态,右侧加汇总条与克制空态;取消固定大高度,模块内滚动。 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
b42b6481f5
commit
5b3ebad757
+65
-40
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="金牛集团管理端" />
|
<meta name="description" content="金牛集团管理端" />
|
||||||
<title>管理端 · 金牛集团</title>
|
<title>管理端 · 金牛集团</title>
|
||||||
<link rel="stylesheet" href="design-system.css?v=6" />
|
<link rel="stylesheet" href="design-system.css?v=7" />
|
||||||
</head>
|
</head>
|
||||||
<body data-portal="admin">
|
<body data-portal="admin">
|
||||||
<a class="skip-link" href="#main-content">跳到主要内容</a>
|
<a class="skip-link" href="#main-content">跳到主要内容</a>
|
||||||
@@ -82,49 +82,74 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" id="companyMasterDetail" style="margin-top: 14px; padding: 0; overflow: hidden;">
|
<div class="card dash-master" id="companyMasterDetail">
|
||||||
|
<div class="card-head dash-master-head">
|
||||||
|
<span class="card-title" style="min-width: 0;">
|
||||||
|
公司往来合计 · 往来明细
|
||||||
|
<span class="sub" id="dashListSub">单位:万元 · 左侧选择公司,右侧查看其往来明细</span>
|
||||||
|
</span>
|
||||||
|
<div class="dash-master-head-actions">
|
||||||
|
<span class="meta" id="dashMasterMeta">—</span>
|
||||||
|
<input class="input" id="company-search" aria-label="搜索公司" placeholder="搜索公司…" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grid grid-480-1 dash-master-split">
|
<div class="grid grid-480-1 dash-master-split">
|
||||||
<div class="dash-master-pane dash-master-pane--list">
|
<div class="dash-master-pane dash-master-pane--list">
|
||||||
<div class="card-head dash-master-head">
|
<div class="dash-company-list" id="dashCompanyRows" role="listbox" aria-label="公司往来合计">
|
||||||
<span class="card-title" style="min-width: 0;">公司往来合计<span class="sub" id="dashListSub" style="display: inline; margin-left: 6px;">单位:万元</span></span>
|
<div class="dash-company-loading muted">加载中…</div>
|
||||||
<input class="input" id="company-search" aria-label="搜索公司" placeholder="搜索…" style="width: 120px; min-height: 28px; padding: 4px 8px; flex: none;" />
|
|
||||||
</div>
|
|
||||||
<div class="table-wrap dash-master-scroll">
|
|
||||||
<table class="ds-table ds-table--compact" id="dashCompanyTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>公司</th>
|
|
||||||
<th class="num-col">明细笔数</th>
|
|
||||||
<th class="num-col">期末净值</th>
|
|
||||||
<th id="dashStatusHead">状态</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="dashCompanyRows">
|
|
||||||
<tr class="loading-row"><td colspan="4">加载中…</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dash-master-pane">
|
<div class="dash-master-pane dash-master-pane--detail">
|
||||||
<div class="card-head dash-master-head">
|
<div class="dash-detail-toolbar">
|
||||||
<span class="card-title" id="dashDetailTitle">公司往来明细<span class="sub" id="dashDetailSub">选择左侧公司查看会计式分级明细</span></span>
|
<div class="dash-detail-heading">
|
||||||
|
<span class="dash-detail-name" id="dashDetailTitle">请选择左侧公司</span>
|
||||||
|
<span class="meta" id="dashDetailSub">按对方公司分组 · 二级默认收起 · 已确认 / 待确认分列</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap dash-master-scroll" id="dashDetailWrap">
|
<div class="dash-detail-summary" id="dashDetailSummary" hidden>
|
||||||
<table class="ds-table ds-table--compact" id="dashDetailTable">
|
<div class="dash-summary-item">
|
||||||
<thead>
|
<span class="dash-summary-label">笔数</span>
|
||||||
<tr>
|
<span class="dash-summary-value num" id="dashSumCount">0</span>
|
||||||
<th>对方 / 摘要</th>
|
</div>
|
||||||
<th class="num-col">笔数</th>
|
<div class="dash-summary-item">
|
||||||
<th class="num-col">期初</th>
|
<span class="dash-summary-label">期初</span>
|
||||||
<th class="num-col">本期借方</th>
|
<span class="dash-summary-value num" id="dashSumOpening">—</span>
|
||||||
<th class="num-col">本期贷方</th>
|
</div>
|
||||||
<th class="num-col">期末</th>
|
<div class="dash-summary-item">
|
||||||
</tr>
|
<span class="dash-summary-label">本期借方</span>
|
||||||
</thead>
|
<span class="dash-summary-value num" id="dashSumDebit">0.00</span>
|
||||||
<tbody id="dashDetailRows">
|
</div>
|
||||||
<tr><td colspan="6" class="muted">请选择左侧公司</td></tr>
|
<div class="dash-summary-item">
|
||||||
</tbody>
|
<span class="dash-summary-label">本期贷方</span>
|
||||||
</table>
|
<span class="dash-summary-value num" id="dashSumCredit">0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-summary-item">
|
||||||
|
<span class="dash-summary-label">期末</span>
|
||||||
|
<span class="dash-summary-value num" id="dashSumEnding">0.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dash-detail-body" id="dashDetailBody">
|
||||||
|
<div class="table-wrap dash-master-scroll" id="dashDetailWrap">
|
||||||
|
<table class="ds-table ds-table--compact" id="dashDetailTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>对方 / 摘要</th>
|
||||||
|
<th class="num-col">笔数</th>
|
||||||
|
<th class="num-col">期初</th>
|
||||||
|
<th class="num-col">本期借方</th>
|
||||||
|
<th class="num-col">本期贷方</th>
|
||||||
|
<th class="num-col">期末</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dashDetailRows">
|
||||||
|
<tr><td colspan="6" class="muted">请选择左侧公司</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="dash-detail-empty" id="dashDetailEmpty" hidden>
|
||||||
|
<div class="e-title">该公司期间内暂无已归集往来</div>
|
||||||
|
<div class="e-desc" id="dashDetailEmptyDesc">期初 — · 导入流水并归集后在此展示</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -917,6 +942,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
|
<div class="toast-region" id="toastRegion" aria-live="polite"></div>
|
||||||
<script src="app.js?v=6"></script>
|
<script src="app.js?v=7"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+144
-38
@@ -1161,33 +1161,43 @@ function updateDashAuditCard(audit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderDashCompanyRows(companies, selectedId) {
|
function renderDashCompanyRows(companies, selectedId) {
|
||||||
const tbody = $("#dashCompanyRows");
|
const list = $("#dashCompanyRows");
|
||||||
if (!tbody) return;
|
if (!list) return;
|
||||||
if (!companies.length) {
|
if (!companies.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="4" class="muted">暂无公司或尚无归集往来</td></tr>';
|
list.innerHTML = '<div class="dash-company-empty muted">暂无公司或尚无归集往来</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tbody.replaceChildren(...companies.map((company) => {
|
const monthLabel = state.dashPeriodMonth ? `${state.dashPeriodMonth}月` : "";
|
||||||
const tr = document.createElement("tr");
|
list.replaceChildren(...companies.map((company) => {
|
||||||
tr.className = "clickable";
|
const item = document.createElement("div");
|
||||||
tr.dataset.companyId = company.id;
|
item.className = "dash-company-item";
|
||||||
|
item.setAttribute("role", "option");
|
||||||
|
item.dataset.companyId = company.id;
|
||||||
|
item.tabIndex = 0;
|
||||||
if (String(company.id) === String(selectedId)) {
|
if (String(company.id) === String(selectedId)) {
|
||||||
tr.classList.add("is-selected");
|
item.classList.add("is-selected");
|
||||||
|
item.setAttribute("aria-selected", "true");
|
||||||
|
} else {
|
||||||
|
item.setAttribute("aria-selected", "false");
|
||||||
}
|
}
|
||||||
const nameTd = document.createElement("td");
|
const main = document.createElement("div");
|
||||||
nameTd.className = "dash-company-name";
|
main.className = "dash-company-main";
|
||||||
nameTd.title = company.name;
|
const name = document.createElement("span");
|
||||||
nameTd.textContent = company.name;
|
name.className = "dash-company-name";
|
||||||
const countTd = document.createElement("td");
|
name.title = company.name;
|
||||||
countTd.className = "num-col";
|
name.textContent = company.name;
|
||||||
countTd.textContent = String(company.detail_count ?? 0);
|
const meta = document.createElement("span");
|
||||||
const netTd = document.createElement("td");
|
meta.className = "dash-company-meta";
|
||||||
netTd.className = `num-col ${netClass(company.net_wan)}`;
|
const statusLabel = company.period_status_label || "—";
|
||||||
netTd.textContent = formatWan(company.net_wan);
|
meta.textContent = monthLabel
|
||||||
const statusTd = document.createElement("td");
|
? `${company.detail_count ?? 0}笔 · ${monthLabel} ${statusLabel}`
|
||||||
statusTd.innerHTML = statusPill(company.period_status_label);
|
: `${company.detail_count ?? 0}笔 · ${statusLabel}`;
|
||||||
tr.append(nameTd, countTd, netTd, statusTd);
|
main.append(name, meta);
|
||||||
return tr;
|
const net = document.createElement("span");
|
||||||
|
net.className = `dash-company-net ${netClass(company.net_wan)}`;
|
||||||
|
net.textContent = formatWan(company.net_wan);
|
||||||
|
item.append(main, net);
|
||||||
|
return item;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1199,22 +1209,94 @@ function chevronSvg(expanded) {
|
|||||||
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width:12px;height:12px;display:inline-block;vertical-align:-1px;margin-right:6px;"><path d="m9 18 6-6-6-6"/></svg>';
|
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width:12px;height:12px;display:inline-block;vertical-align:-1px;margin-right:6px;"><path d="m9 18 6-6-6-6"/></svg>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDashDetail(payload, { expandFirst = false } = {}) {
|
function formatPlainWan(value) {
|
||||||
|
const num = Number(value);
|
||||||
|
if (!Number.isFinite(num)) return "0.00";
|
||||||
|
return Math.abs(num).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDashDetailSummary(company, payload) {
|
||||||
|
const bar = $("#dashDetailSummary");
|
||||||
|
if (!bar) return;
|
||||||
|
if (!company) {
|
||||||
|
bar.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bar.hidden = false;
|
||||||
|
const count = payload?.groups
|
||||||
|
? payload.groups.reduce((sum, g) => sum + Number(g.count || 0), 0)
|
||||||
|
: Number(company.detail_count || 0);
|
||||||
|
const debit = payload?.groups
|
||||||
|
? payload.groups.reduce((sum, g) => sum + Number(g.debit_wan || 0), 0)
|
||||||
|
: Number(company.debit_wan || 0);
|
||||||
|
const credit = payload?.groups
|
||||||
|
? payload.groups.reduce((sum, g) => sum + Number(g.credit_wan || 0), 0)
|
||||||
|
: Number(company.credit_wan || 0);
|
||||||
|
const ending = payload?.groups
|
||||||
|
? payload.groups.reduce((sum, g) => sum + Number(g.ending_wan || 0), 0)
|
||||||
|
: Number(company.net_wan || 0);
|
||||||
|
const setText = (id, text, className = "") => {
|
||||||
|
const el = $(id);
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = text;
|
||||||
|
el.className = `dash-summary-value num ${className}`.trim();
|
||||||
|
};
|
||||||
|
setText("#dashSumCount", String(count));
|
||||||
|
setText("#dashSumOpening", "—", "meta");
|
||||||
|
setText("#dashSumDebit", formatPlainWan(debit));
|
||||||
|
setText("#dashSumCredit", formatPlainWan(credit));
|
||||||
|
setText("#dashSumEnding", formatWan(ending), netClass(ending));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDashDetailEmpty(company) {
|
||||||
|
const wrap = $("#dashDetailWrap");
|
||||||
|
const empty = $("#dashDetailEmpty");
|
||||||
|
const desc = $("#dashDetailEmptyDesc");
|
||||||
|
if (wrap) wrap.hidden = true;
|
||||||
|
if (empty) empty.hidden = false;
|
||||||
|
if (desc) {
|
||||||
|
const cutoff = state.dashCutoff || "—";
|
||||||
|
// 方案 A 空态:两行克制说明;期初口径仍为不可用(—),不伪造 0.00
|
||||||
|
desc.textContent = `期初 — · 截至 ${cutoff} 无明细记录 · 导入流水并归集后在此展示`;
|
||||||
|
}
|
||||||
|
updateDashDetailSummary(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDashDetailTable() {
|
||||||
|
const wrap = $("#dashDetailWrap");
|
||||||
|
const empty = $("#dashDetailEmpty");
|
||||||
|
if (wrap) wrap.hidden = false;
|
||||||
|
if (empty) empty.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDashDetail(payload, { expandFirst = false, company = null } = {}) {
|
||||||
const tbody = $("#dashDetailRows");
|
const tbody = $("#dashDetailRows");
|
||||||
const title = $("#dashDetailTitle");
|
const title = $("#dashDetailTitle");
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
|
const selected = company || (state.dashCompanies || []).find(
|
||||||
|
(c) => String(c.id) === String(payload?.company_id || state.dashSelectedId)
|
||||||
|
) || null;
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
|
if (title) title.textContent = "请选择左侧公司";
|
||||||
|
showDashDetailTable();
|
||||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">请选择左侧公司</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="muted">请选择左侧公司</td></tr>';
|
||||||
|
updateDashDetailSummary(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (title) {
|
if (title) title.textContent = payload.company_name || selected?.name || "公司往来明细";
|
||||||
title.innerHTML = `公司往来明细 · ${payload.company_name}<span class="sub" id="dashDetailSub">按对方公司分组 · 二级默认收起 · 期初待 B-45</span>`;
|
const sub = $("#dashDetailSub");
|
||||||
}
|
if (sub) sub.textContent = "按对方公司分组 · 二级默认收起 · 已确认 / 待确认分列";
|
||||||
|
|
||||||
const groups = payload.groups || [];
|
const groups = payload.groups || [];
|
||||||
if (!groups.length) {
|
if (!groups.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">该公司区间内暂无已归集往来</td></tr>';
|
showDashDetailEmpty(selected || { detail_count: 0, debit_wan: 0, credit_wan: 0, net_wan: 0 });
|
||||||
|
tbody.replaceChildren();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showDashDetailTable();
|
||||||
|
updateDashDetailSummary(selected, payload);
|
||||||
const rows = [];
|
const rows = [];
|
||||||
groups.forEach((group, index) => {
|
groups.forEach((group, index) => {
|
||||||
const expanded = expandFirst && index === 0;
|
const expanded = expandFirst && index === 0;
|
||||||
@@ -1268,7 +1350,12 @@ async function resolveDashStartDate() {
|
|||||||
async function loadDashCompanyDetail(companyId) {
|
async function loadDashCompanyDetail(companyId) {
|
||||||
if (!companyId) return;
|
if (!companyId) return;
|
||||||
state.dashSelectedId = companyId;
|
state.dashSelectedId = companyId;
|
||||||
|
const company = (state.dashCompanies || []).find((c) => String(c.id) === String(companyId)) || null;
|
||||||
renderDashCompanyRows(state.dashCompanies || [], companyId);
|
renderDashCompanyRows(state.dashCompanies || [], companyId);
|
||||||
|
const title = $("#dashDetailTitle");
|
||||||
|
if (title && company) title.textContent = company.name;
|
||||||
|
updateDashDetailSummary(company, null);
|
||||||
|
showDashDetailTable();
|
||||||
const tbody = $("#dashDetailRows");
|
const tbody = $("#dashDetailRows");
|
||||||
if (tbody) tbody.innerHTML = '<tr class="loading-row"><td colspan="6">加载中…</td></tr>';
|
if (tbody) tbody.innerHTML = '<tr class="loading-row"><td colspan="6">加载中…</td></tr>';
|
||||||
const from = await resolveDashStartDate();
|
const from = await resolveDashStartDate();
|
||||||
@@ -1278,7 +1365,7 @@ async function loadDashCompanyDetail(companyId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await response.json().catch(() => null);
|
const result = await response.json().catch(() => null);
|
||||||
renderDashDetail(result);
|
renderDashDetail(result, { company });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAdminDashboard() {
|
async function loadAdminDashboard() {
|
||||||
@@ -1286,22 +1373,30 @@ async function loadAdminDashboard() {
|
|||||||
const from = await resolveDashStartDate();
|
const from = await resolveDashStartDate();
|
||||||
const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
|
const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
|
||||||
if (!response?.ok) {
|
if (!response?.ok) {
|
||||||
$("#dashCompanyRows").innerHTML = '<tr><td colspan="4" class="muted">总览加载失败</td></tr>';
|
$("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = await response.json().catch(() => null);
|
const data = await response.json().catch(() => null);
|
||||||
if (!data || data.status !== "ok") {
|
if (!data || data.status !== "ok") {
|
||||||
$("#dashCompanyRows").innerHTML = '<tr><td colspan="4" class="muted">总览加载失败</td></tr>';
|
$("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.dashCompanies = data.companies || [];
|
state.dashCompanies = data.companies || [];
|
||||||
state.dashFrom = data.from_date;
|
state.dashFrom = data.from_date;
|
||||||
state.dashCutoff = data.cutoff;
|
state.dashCutoff = data.cutoff;
|
||||||
|
state.dashPeriodMonth = data.period_month;
|
||||||
updateDashAuditCard(data.audit);
|
updateDashAuditCard(data.audit);
|
||||||
const statusHead = $("#dashStatusHead");
|
|
||||||
if (statusHead && data.period_month) statusHead.textContent = `${data.period_month}月状态`;
|
|
||||||
const listSub = $("#dashListSub");
|
const listSub = $("#dashListSub");
|
||||||
if (listSub) listSub.textContent = `${data.from_date} 至 ${data.cutoff} · 单位:万元`;
|
if (listSub) {
|
||||||
|
listSub.textContent = `${data.from_date} 至 ${data.cutoff} · 单位:万元 · 左侧选择公司,右侧查看其往来明细`;
|
||||||
|
}
|
||||||
|
const meta = $("#dashMasterMeta");
|
||||||
|
if (meta) {
|
||||||
|
const totals = data.totals || {};
|
||||||
|
const companyCount = totals.company_count ?? state.dashCompanies.length;
|
||||||
|
const detailCount = totals.detail_count ?? 0;
|
||||||
|
meta.textContent = `${companyCount} 家公司 · 明细 ${detailCount} 笔`;
|
||||||
|
}
|
||||||
const selected = state.dashSelectedId || state.dashCompanies[0]?.id;
|
const selected = state.dashSelectedId || state.dashCompanies[0]?.id;
|
||||||
renderDashCompanyRows(state.dashCompanies, selected);
|
renderDashCompanyRows(state.dashCompanies, selected);
|
||||||
if (selected) await loadDashCompanyDetail(selected);
|
if (selected) await loadDashCompanyDetail(selected);
|
||||||
@@ -1309,10 +1404,21 @@ async function loadAdminDashboard() {
|
|||||||
|
|
||||||
function initDashboard() {
|
function initDashboard() {
|
||||||
if (!$("#companyMasterDetail")) return;
|
if (!$("#companyMasterDetail")) return;
|
||||||
|
const selectCompany = (companyId) => {
|
||||||
|
if (!companyId) return;
|
||||||
|
loadDashCompanyDetail(companyId);
|
||||||
|
};
|
||||||
$("#dashCompanyRows")?.addEventListener("click", (event) => {
|
$("#dashCompanyRows")?.addEventListener("click", (event) => {
|
||||||
const tr = event.target.closest("tr[data-company-id]");
|
const item = event.target.closest(".dash-company-item[data-company-id]");
|
||||||
if (!tr) return;
|
if (!item) return;
|
||||||
loadDashCompanyDetail(tr.dataset.companyId);
|
selectCompany(item.dataset.companyId);
|
||||||
|
});
|
||||||
|
$("#dashCompanyRows")?.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key !== "Enter" && event.key !== " ") return;
|
||||||
|
const item = event.target.closest(".dash-company-item[data-company-id]");
|
||||||
|
if (!item) return;
|
||||||
|
event.preventDefault();
|
||||||
|
selectCompany(item.dataset.companyId);
|
||||||
});
|
});
|
||||||
$("#dashDetailRows")?.addEventListener("click", (event) => {
|
$("#dashDetailRows")?.addEventListener("click", (event) => {
|
||||||
const tr = event.target.closest("tr[data-peer-group]");
|
const tr = event.target.closest("tr[data-peer-group]");
|
||||||
@@ -1330,8 +1436,8 @@ function initDashboard() {
|
|||||||
});
|
});
|
||||||
$("#company-search")?.addEventListener("input", function () {
|
$("#company-search")?.addEventListener("input", function () {
|
||||||
const q = this.value.trim();
|
const q = this.value.trim();
|
||||||
$$("#dashCompanyRows tr[data-company-id]").forEach((tr) => {
|
$$("#dashCompanyRows .dash-company-item[data-company-id]").forEach((item) => {
|
||||||
tr.style.display = (!q || tr.textContent.includes(q)) ? "" : "none";
|
item.style.display = (!q || item.textContent.includes(q)) ? "" : "none";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
loadAdminDashboard();
|
loadAdminDashboard();
|
||||||
|
|||||||
+229
-14
@@ -216,23 +216,176 @@ p { margin: 0; }
|
|||||||
/* 管理端首页主从:左栏约 480px(定稿 HEL-153),右侧明细更宽 */
|
/* 管理端首页主从:左栏约 480px(定稿 HEL-153),右侧明细更宽 */
|
||||||
.grid-480-1 { grid-template-columns: minmax(0, 480px) minmax(0, 1fr); align-items: stretch; }
|
.grid-480-1 { grid-template-columns: minmax(0, 480px) minmax(0, 1fr); align-items: stretch; }
|
||||||
|
|
||||||
|
/* 方案 A:一体卡片 · 分栏主从(HEL-162) */
|
||||||
|
.dash-master {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.dash-master-head {
|
||||||
|
padding: 12px 16px 10px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.dash-master-head .card-title .sub {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
.dash-master-head-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex: none;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.dash-master-head-actions .input {
|
||||||
|
width: 132px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
.dash-master-split {
|
.dash-master-split {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
height: 492px;
|
/* 少数据自适应;整卡(含共享头)目标约 340px;多数据模块内滚动 */
|
||||||
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
.dash-master-pane {
|
.dash-master-pane {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.dash-master-pane--list {
|
.dash-master-pane--list {
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.dash-master-head {
|
.dash-company-list {
|
||||||
padding: 10px 14px 8px;
|
flex: 1;
|
||||||
margin-bottom: 0;
|
min-height: 0;
|
||||||
gap: 8px;
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.dash-company-loading,
|
||||||
|
.dash-company-empty {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.dash-company-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 14px 8px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s ease;
|
||||||
|
}
|
||||||
|
.dash-company-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.dash-company-item:hover {
|
||||||
|
background: var(--fg-soft);
|
||||||
|
}
|
||||||
|
.dash-company-item.is-selected {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
box-shadow: inset 3px 0 0 var(--accent);
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
.dash-company-item.is-selected:hover {
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
.dash-company-main {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.dash-company-name {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 550;
|
||||||
|
color: var(--fg);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.dash-company-meta {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.dash-company-net {
|
||||||
|
flex: none;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: right;
|
||||||
|
min-width: 4.5em;
|
||||||
|
}
|
||||||
|
.dash-detail-toolbar {
|
||||||
|
padding: 10px 14px 0;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.dash-detail-heading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.dash-detail-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 650;
|
||||||
|
color: var(--fg);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.dash-detail-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
gap: 0;
|
||||||
|
margin: 10px 14px 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--bg);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.dash-summary-item {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.dash-summary-item:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.dash-summary-label {
|
||||||
|
display: block;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.dash-summary-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.dash-detail-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.dash-master-scroll {
|
.dash-master-scroll {
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -241,6 +394,32 @@ p { margin: 0; }
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
.dash-detail-empty {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 24px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.dash-detail-empty .e-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.dash-detail-empty .e-desc {
|
||||||
|
max-width: 360px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
.dash-master-scroll[hidden],
|
||||||
|
.dash-detail-empty[hidden],
|
||||||
|
.dash-detail-summary[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
.ds-table.ds-table--compact {
|
.ds-table.ds-table--compact {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
@@ -248,18 +427,16 @@ p { margin: 0; }
|
|||||||
.ds-table.ds-table--compact td {
|
.ds-table.ds-table--compact td {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
.ds-table.ds-table--compact th {
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.ds-table tbody tr.is-selected {
|
.ds-table tbody tr.is-selected {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
}
|
}
|
||||||
.ds-table tbody tr.is-selected:hover {
|
.ds-table tbody tr.is-selected:hover {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
}
|
}
|
||||||
.dash-company-name {
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 280px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.row { display: flex; align-items: center; gap: 10px; }
|
.row { display: flex; align-items: center; gap: 10px; }
|
||||||
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||||
.stack { display: flex; flex-direction: column; gap: 14px; }
|
.stack { display: flex; flex-direction: column; gap: 14px; }
|
||||||
@@ -271,15 +448,53 @@ p { margin: 0; }
|
|||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.grid-4, .grid-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.grid-4, .grid-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
.grid-3, .grid-2-1, .grid-1-2, .grid-3-2, .grid-480-1 { grid-template-columns: minmax(0, 1fr); }
|
.grid-3, .grid-2-1, .grid-1-2, .grid-3-2, .grid-480-1 { grid-template-columns: minmax(0, 1fr); }
|
||||||
.dash-master-split { height: auto; max-height: none; }
|
.dash-master-split {
|
||||||
.dash-master-pane--list { border-right: 0; border-bottom: 1px solid var(--border); }
|
max-height: none;
|
||||||
.dash-master-pane { max-height: 420px; }
|
}
|
||||||
|
.dash-master-pane--list {
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
max-height: 280px;
|
||||||
|
}
|
||||||
|
.dash-master-pane--detail {
|
||||||
|
max-height: 420px;
|
||||||
|
}
|
||||||
|
.dash-detail-summary {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.dash-summary-item:nth-child(3) {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.dash-summary-item:nth-child(n+4) {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
.shell { grid-template-columns: 1fr; }
|
.shell { grid-template-columns: 1fr; }
|
||||||
.sidebar { position: static; height: auto; }
|
.sidebar { position: static; height: auto; }
|
||||||
.grid-2 { grid-template-columns: 1fr; }
|
.grid-2 { grid-template-columns: 1fr; }
|
||||||
.content { padding: 16px 16px 48px; }
|
.content { padding: 16px 16px 48px; }
|
||||||
|
.dash-master-head-actions {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.dash-master-head-actions .input {
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.dash-detail-summary {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.dash-summary-item:nth-child(2n) {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.dash-summary-item:nth-child(3) {
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.dash-summary-item:nth-child(n+3) {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 数据表 ────────────────────────────────────────────────────── */
|
/* ─── 数据表 ────────────────────────────────────────────────────── */
|
||||||
|
|||||||
Reference in New Issue
Block a user