// home.js — 首页渲染 + 财务趋势图 function renderHome() { const { summary, financeMonthly } = state.data; const m = summary.metrics; const moneyInt = (v) => `${Math.round(Number(v || 0)).toLocaleString("zh-CN")} 元`; const rows1 = [ ["年度累计", moneyInt(m.signed_annual || m.signed_amount)], ["季度累计", moneyInt(m.signed_q2 || 0)], ["本月新增", moneyInt(m.signed_month || 0)], ["上季度累计", moneyInt(m.signed_prev_q || 0)], ["上月累计", moneyInt(m.signed_prev_month || 0)], ]; const rows2 = [ ["年度累计", moneyInt(m.revenue_annual)], ["季度累计", moneyInt(m.revenue_q2)], ["本月新增", moneyInt(m.monthly_revenue)], ["上季度累计", moneyInt(m.revenue_prev_q || 0)], ["上月累计", moneyInt(m.revenue_prev_month || 0)], ]; const rows3 = [ ["年度累计", moneyInt(m.gross_annual)], ["季度累计", moneyInt(m.gross_q2)], ["本月新增", moneyInt(m.monthly_net_profit)], ["上季度累计", moneyInt(m.gross_prev_q || 0)], ["上月累计", moneyInt(m.gross_prev_month || 0)], ]; const rows4 = [ ["年度累计", moneyInt(m.payment_annual || 0)], ["季度累计", moneyInt(m.payment_q2 || 0)], ["本月新增", moneyInt(m.payment_month || 0)], ["上季度累计", moneyInt(m.payment_prev_q || 0)], ["上月累计", moneyInt(m.payment_prev_month || 0)], ]; const rows5 = [ ["年度累计", moneyInt(m.cost_annual || 0)], ["季度累计", moneyInt(m.cost_q2 || 0)], ["本月新增", moneyInt(m.cost_month || 0)], ["上季度累计", moneyInt(m.cost_prev_q || 0)], ["上月累计", moneyInt(m.cost_prev_month || 0)], ]; const rows7 = [ ["年度累计", moneyInt(m.paid_annual || 0)], ["季度累计", moneyInt(m.paid_q2 || 0)], ["本月新增", moneyInt(m.paid_month || 0)], ["上季度累计", moneyInt(m.paid_prev_q || 0)], ["上月累计", moneyInt(m.paid_prev_month || 0)], ]; const rows8 = [ ["年度累计", moneyInt(m.cashflow_annual || 0)], ["季度累计", moneyInt(m.cashflow_q2 || 0)], ["本月新增", moneyInt(m.cashflow_month || 0)], ["上季度累计", moneyInt(m.cashflow_prev_q || 0)], ["上月累计", moneyInt(m.cashflow_prev_month || 0)], ]; const rows9 = [ ["年度累计", moneyInt(m.profit_annual || 0)], ["季度累计", moneyInt(m.profit_q2 || 0)], ["本月新增", moneyInt(m.profit_month || 0)], ["上季度累计", moneyInt(m.profit_prev_q || 0)], ["上月累计", moneyInt(m.profit_prev_month || 0)], ]; const qoq = (cur, prev) => { if (!prev) return ''; const pct = Math.round((cur - prev) / prev * 100); const cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; const sign = pct >= 0 ? '+' : ''; return '' + sign + pct + '%'; }; const COLORS = ['text-slate-700','text-blue-600','text-green-600','text-amber-600','text-rose-600','text-purple-600','text-cyan-600','text-indigo-600']; const LABELS = ['合同金额','确收金额','确收毛利','回款金额','应付金额','已付','现金流','项目利润']; const Q_FIELDS = [m.signed_q2||0, m.revenue_q2, m.gross_q2, m.payment_q2||0, m.cost_q2||0, m.paid_q2||0, m.cashflow_q2||0, m.profit_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.paid_prev_q||0, m.cashflow_prev_q||0, m.profit_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.paid_month||0, m.cashflow_month||0, m.profit_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.paid_prev_month||0, m.cashflow_prev_month||0, m.profit_prev_month||0]; const ROWS = [rows1, rows2, rows3, rows4, rows5, rows7, rows8, rows9]; var thead = '指标年度累计季度累计季环比本月新增月环比上季度累计上月累计'; var tbody = ''; for (var ri = 0; ri < 8; ri++) { var vals = ROWS[ri]; tbody += '' + LABELS[ri] + '' + '' + vals[0][1] + '' + '' + vals[1][1] + '' + '' + qoq(Q_FIELDS[ri], Q_PREV[ri]) + '' + '' + vals[2][1] + '' + '' + qoq(M_FIELDS[ri], M_PREV[ri]) + '' + '' + vals[3][1] + '' + '' + vals[4][1] + ''; } document.querySelector("#home").innerHTML = `
${state.tenant === "总工作台" ? "" : `
${[ ["项目管理", m.total_projects, "finance"], ["重点工作与台账", m.total_proposals, "projects"], ["业务方案", m.total_products, "proposals"], ["产品迭代", m.upcoming_products, "products"], ].map(([label, value, tab]) => ``).join("")}
`} ${card(`

部门经营情况

${(() => { var d = [ ['部门利润', [m.profit_annual||0, m.profit_q2||0, m.profit_month||0, m.profit_prev_q||0, m.profit_prev_month||0], [m.profit_q2||0,m.profit_prev_q||0], [m.profit_month||0,m.profit_prev_month||0]], ['部门费用', [m.expense_annual||0, m.expense_q2||0, (m.expense_month||0), m.expense_prev_q||0, m.expense_prev_month||0], [m.expense_q2||0,m.expense_prev_q||0], [(m.expense_month||0),m.expense_prev_month||0]], ['部门净利', [ (m.profit_annual||0)-(m.expense_annual||0), (m.profit_q2||0)-(m.expense_q2||0), (m.profit_month||0)-(m.expense_month||0), (m.profit_prev_q||0)-(m.expense_prev_q||0), (m.profit_prev_month||0)-(m.expense_prev_month||0) ], [ (m.profit_q2||0)-(m.expense_q2||0), (m.profit_prev_q||0)-(m.expense_prev_q||0) ], [ (m.profit_month||0)-(m.expense_month||0), (m.profit_prev_month||0)-(m.expense_prev_month||0) ]] ]; var h = ''; for (var di = 0; di < 3; di++) { var r = d[di]; h += '' + '' + '' + '' + '' + '' + '' + ''; } return h; })()}
指标年度累计季度累计季环比本月新增月环比上季度累计上月累计
' + r[0] + '' + moneyInt(r[1][0]) + '' + moneyInt(r[1][1]) + '' + qoq(r[2][0], r[2][1]) + '' + moneyInt(r[1][2]) + '' + qoq(r[3][0], r[3][1]) + '' + moneyInt(r[1][3]) + '' + moneyInt(r[1][4]) + '
`, "p-4")} ${card(`

业务(项目)财务概览

${thead}${tbody}
`, "p-4")}
${card(`

月度签约趋势

2026
`, "p-4")} ${card(`

月度确收与毛利

2026
`, "p-4")} ${card(`

月度回款与已付

2026
`, "p-4")} ${card(`

月度项目利润

2026
`, "p-4")}
${card(`

近期动态

${summary.recent.map((r) => `
${r.content}
${r.followed_at}
`).join("")}
`, "p-5")}
`; renderCharts(financeMonthly); } function chartOptions(yCallback) { return { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "bottom", labels: { boxWidth: 12, font: { size: 11 } } } }, scales: { x: { ticks: { font: { size: 10 } }, grid: { display: false } }, y: { ticks: { font: { size: 11 }, callback: yCallback } }, }, }; } const moneyTick = (v) => v >= 10000 ? (v / 10000).toFixed(0) + "万" : v; const monthLabels = (data) => data.map((x) => parseInt(x.month.split("-")[1]) + "月"); function renderCharts(data) { const labels = monthLabels(data); const baseOpts = chartOptions(moneyTick); // 图1:月度签约 const c1 = document.querySelector("#chartSign"); if (c1 && window.Chart) { if (state.chart) state.chart.destroy(); state.chart = new Chart(c1, { type: "line", data: { labels, datasets: [ { label: "签约金额", data: data.map((x) => x.sign || 0), borderColor: "#6366f1", backgroundColor: "rgba(99,102,241,0.06)", fill: true, tension: 0.3 }, ]}, options: baseOpts, }); } // 图2:月度确收与毛利 const c2 = document.querySelector("#chartRev"); if (c2 && window.Chart) { if (state.chart2) state.chart2.destroy(); state.chart2 = new Chart(c2, { type: "line", data: { labels, datasets: [ { label: "确收", data: data.map((x) => x.revenue || 0), borderColor: "#2563eb", backgroundColor: "rgba(37,99,235,0.06)", fill: true, tension: 0.3 }, { label: "毛利", data: data.map((x) => x.gross || 0), borderColor: "#059669", backgroundColor: "rgba(5,150,105,0.06)", fill: true, tension: 0.3 }, ]}, options: baseOpts, }); } // 图3:月度回款与已付 const c3 = document.querySelector("#chartCash"); if (c3 && window.Chart) { if (state.chart3) state.chart3.destroy(); state.chart3 = new Chart(c3, { type: "line", data: { labels, datasets: [ { label: "回款", data: data.map((x) => x.payment || 0), borderColor: "#d97706", backgroundColor: "rgba(217,119,6,0.06)", fill: true, tension: 0.3 }, { label: "已付", data: data.map((x) => x.cost || 0), borderColor: "#7c3aed", backgroundColor: "rgba(124,58,237,0.06)", fill: true, tension: 0.3 }, ]}, options: baseOpts, }); } // 图4:月度项目利润 const c4 = document.querySelector("#chartProfit"); if (c4 && window.Chart) { if (state.chart4) state.chart4.destroy(); state.chart4 = new Chart(c4, { type: "line", data: { labels, datasets: [ { label: "利润", data: data.map((x) => (x.gross || 0)), borderColor: "#6366f1", backgroundColor: "rgba(99,102,241,0.06)", fill: true, tension: 0.3 }, ]}, options: baseOpts, }); } }