feat: 首页财务概览重构 — 5卡片合并为表格 + 费用列 + 环比列
- 5张独立财务卡片合并为一张表格(行=时间维度,列=6项指标) - 新增费用列(数据源expense_records,单/多租户路径均支持) - 每列增加环比列(季度累计行=季度环比,本月新增行=月度环比) - 环比:正值绿+xx%,负值红-xx% - 后端新增expense_annual/q2/month/prev_q/prev_month指标
This commit is contained in:
@@ -39,24 +39,48 @@ function renderHome() {
|
||||
["上季度累计", moneyInt(m.cost_prev_q || 0)],
|
||||
["上月累计", moneyInt(m.cost_prev_month || 0)],
|
||||
];
|
||||
let _cardId = 0;
|
||||
const tblCard = (title, rows) => {
|
||||
const id = 'finCard' + (++_cardId);
|
||||
const visible = rows.slice(0, 3);
|
||||
const extra = rows.slice(3);
|
||||
const rowHtml = (r) => r.map(([label, value]) => `<tr class="border-b border-slate-100 last:border-0"><td class="py-2 pr-4 text-slate-500">${label}</td><td class="py-2 text-right font-semibold text-slate-800">${value}</td></tr>`).join("");
|
||||
return card(`<h3 class="text-sm font-bold text-slate-700 mb-3">${title}</h3><table class="w-full text-sm"><tbody>${rowHtml(visible)}</tbody></table><table class="w-full text-sm hidden" id="${id}"><tbody>${rowHtml(extra)}</tbody></table><button class="w-full text-center py-1 text-xs text-slate-400 hover:text-slate-600" onclick="toggleFinCard('${id}')" id="${id}_btn"><i data-lucide="chevron-down" style="width:14px;height:14px;display:inline"></i></button>`, "p-4");
|
||||
};
|
||||
window.toggleFinCard = (id) => {
|
||||
const t = document.getElementById(id);
|
||||
if (!t) return;
|
||||
t.classList.toggle("hidden");
|
||||
const btn = document.getElementById(id + "_btn");
|
||||
btn.innerHTML = t.classList.contains("hidden")
|
||||
? '<i data-lucide="chevron-down" style="width:14px;height:14px;display:inline"></i>'
|
||||
: '<i data-lucide="chevron-up" style="width:14px;height:14px;display:inline"></i>';
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
const rows6 = [
|
||||
["年度累计", moneyInt(m.expense_annual || 0)],
|
||||
["季度累计", moneyInt(m.expense_q2 || 0)],
|
||||
["本月新增", moneyInt(m.expense_month || 0)],
|
||||
["上季度累计", moneyInt(m.expense_prev_q || 0)],
|
||||
["上月累计", moneyInt(m.expense_prev_month || 0)],
|
||||
];
|
||||
const qoq = (cur, prev) => {
|
||||
if (!prev) return '<span class="text-slate-300">—</span>';
|
||||
const pct = Math.round((cur - prev) / prev * 100);
|
||||
const cls = pct >= 0 ? 'text-green-600' : 'text-red-600';
|
||||
const sign = pct >= 0 ? '+' : '';
|
||||
return '<span class="' + cls + '">' + sign + pct + '%</span>';
|
||||
};
|
||||
const COLORS = ['text-slate-700','text-blue-600','text-green-600','text-amber-600','text-rose-600','text-orange-600'];
|
||||
const LABELS = ['合同金额','确收金额','确收毛利','回款金额','应付金额','费用'];
|
||||
const ALL_ROWS = [rows1, rows2, rows3, rows4, rows5, rows6];
|
||||
// 季度环比:Q 值 vs prev_Q 值;月度环比:month 值 vs prev_month 值
|
||||
const Q_FIELDS = [m.signed_q2||0, m.revenue_q2, m.gross_q2, m.payment_q2||0, m.cost_q2||0, m.expense_q2||0];
|
||||
const Q_PREV = [m.signed_prev_q||0, m.revenue_prev_q||0, m.gross_prev_q||0, m.payment_prev_q||0, m.cost_prev_q||0, m.expense_prev_q||0];
|
||||
const M_FIELDS = [m.signed_month||0, m.monthly_revenue, m.monthly_net_profit, m.payment_month||0, m.cost_month||0, m.expense_month||0];
|
||||
const M_PREV = [m.signed_prev_month||0, m.revenue_prev_month||0, m.gross_prev_month||0, m.payment_prev_month||0, m.cost_prev_month||0, m.expense_prev_month||0];
|
||||
var thead = '<tr class="border-b border-slate-200"><th class="py-2 text-left text-slate-500"></th>';
|
||||
for (var ci = 0; ci < 6; ci++) {
|
||||
thead += '<th class="py-2 text-right font-semibold ' + COLORS[ci] + '">' + LABELS[ci] + '</th><th class="py-2 text-right font-semibold text-slate-400" style="font-size:11px">环比</th>';
|
||||
}
|
||||
thead += '</tr>';
|
||||
var tbody = '';
|
||||
for (var ri = 0; ri < 5; ri++) {
|
||||
var rowLabel = rows1[ri][0];
|
||||
var rowHtml = '<tr class="border-b border-slate-100 last:border-0"><td class="py-2 text-slate-500">' + rowLabel + '</td>';
|
||||
for (var ci = 0; ci < 6; ci++) {
|
||||
rowHtml += '<td class="py-2 text-right font-semibold text-slate-800">' + ALL_ROWS[ci][ri][1] + '</td>';
|
||||
var momHtml = '';
|
||||
if (ri === 1) momHtml = qoq(Q_FIELDS[ci], Q_PREV[ci]); // 季度累计行:季度环比
|
||||
else if (ri === 2) momHtml = qoq(M_FIELDS[ci], M_PREV[ci]); // 本月新增行:月度环比
|
||||
else momHtml = '<span class="text-slate-300">—</span>';
|
||||
rowHtml += '<td class="py-2 text-right" style="font-size:12px">' + momHtml + '</td>';
|
||||
}
|
||||
rowHtml += '</tr>';
|
||||
tbody += rowHtml;
|
||||
}
|
||||
document.querySelector("#home").innerHTML = `
|
||||
<div class="grid gap-5">
|
||||
${state.tenant === "总工作台" ? "" : `<div class="grid grid-cols-4 gap-3">
|
||||
@@ -67,7 +91,7 @@ window.toggleFinCard = (id) => {
|
||||
["产品迭代", m.upcoming_products, "products"],
|
||||
].map(([label, value, tab]) => `<button class="metric-card" onclick="switchTab('${tab}')"><span class="flex items-center gap-2 text-xs text-slate-500"><i data-lucide="gauge"></i>${label}</span><strong class="mt-2 block text-2xl">${value}</strong></button>`).join("")}
|
||||
</div>`}
|
||||
<div class="grid grid-cols-5 gap-5">${tblCard("合同金额", rows1)}${tblCard("确收金额", rows2)}${tblCard("确收毛利", rows3)}${tblCard("回款金额", rows4)}${tblCard("应付金额", rows5)}</div>
|
||||
${card(`<h3 class="text-sm font-bold text-slate-700 mb-3">财务概览</h3><div class="overflow-x-auto"><table class="w-full text-sm"><thead>${thead}</thead><tbody>${tbody}</tbody></table></div>`, "p-4")}
|
||||
<div class="grid grid-cols-3 gap-5">
|
||||
${card(`<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度签约趋势</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="chartSign"></canvas></div>`, "p-4")}
|
||||
${card(`<div class="mb-3 flex items-center justify-between"><h2 class="text-sm font-bold text-slate-600">月度确收与毛利</h2><span class="text-xs text-slate-400">2026</span></div><div style="position:relative;height:200px"><canvas id="chartRev"></canvas></div>`, "p-4")}
|
||||
|
||||
Reference in New Issue
Block a user