From b313c1b6a200cad02b1a724873de13fe59f34253 Mon Sep 17 00:00:00 2001 From: mac Date: Wed, 8 Jul 2026 16:29:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=80=BB=E8=A7=88=E7=B2=BE=E7=AE=80+?= =?UTF-8?q?=E5=AE=9E=E9=99=85=E6=A8=A1=E5=9D=97=E8=A1=A8=E5=A4=B4=E8=B0=83?= =?UTF-8?q?=E6=95=B4+=E5=88=A0=E9=99=A4=E5=88=A9=E6=B6=A6=E5=9B=BE?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 月度/季度总览只显示5行: 合同金额/确收/毛利/成本/已付 - 实际模块项目表头调整为12列新顺序 - 删除月度项目利润图表, 剩余3图改3列布局 - 版本号 v1.0.0.30 --- static/modules/finance.js | 46 +++++++++++++++------------------------ static/modules/plan.js | 42 +++++++++++------------------------ templates/index.html | 2 +- 3 files changed, 32 insertions(+), 58 deletions(-) diff --git a/static/modules/finance.js b/static/modules/finance.js index 0f459c1..dede43b 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -287,10 +287,9 @@ function renderFinance() { }; const SIGNED_COLS = [ { key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' }, - { key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' }, - { key: 'payment', label: '已回款' }, { key: 'paid', label: '已付' }, - { key: 'cashflow', label: '现金流' }, { key: 'exe_rate', label: '执行率' }, { key: 'gross_rate', label: '毛利率' }, - { key: 'pay_rate', label: '回款率' }, { key: 'paid_rate', label: '付款率' }, + { key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'gross_rate', label: '毛利率' }, + { key: 'payment', label: '回款' }, { key: 'cashflow', label: '现金流' }, { key: 'pay_rate', label: '回款率' }, + { key: 'cost', label: '成本' }, { key: 'paid', label: '已付' }, { key: 'paid_rate', label: '付款率' }, ]; const UNSIGNED_COLS = [ { key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' }, @@ -305,7 +304,7 @@ function renderFinance() { return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color]; }); const thead = isUnsigned ? buildThead(UNSIGNED_COLS, 'uns') : buildThead(SIGNED_COLS, 'sig'); - const theadCols = isUnsigned ? 6 : 13; + const theadCols = isUnsigned ? 6 : 12; const tbody = rows.length ? `${rows.join("")}` : `${emptyText}`; const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-6'; @@ -327,7 +326,7 @@ function renderFinance() { const grossR = rev && gross ? Math.round(gross / rev * 100) + '%' : ''; const cf = cashflow ? money(cashflow) : ''; const cfCls = cashflow >= 0 ? 'text-green-600' : 'text-red-600'; - return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${fmtNoUnit(cost)}${fmtNoUnit(payment)}${fmtNoUnit(paid)}${cf}${exe}${grossR}${payR}${paidR}`; + return `${esc(pf.client_name || "")}${esc(pf.customer_name)}${money(pf.sign_amount)}${fmtNoUnit(rev)}${fmtNoUnit(gross)}${grossR}${fmtNoUnit(payment)}${cf}${payR}${fmtNoUnit(cost)}${fmtNoUnit(paid)}${paidR}`; }; // 待签约简版行 const tdRowUnsigned = (pf, cost, gross) => { @@ -937,26 +936,22 @@ function renderFinanceOverview() { const card = (body, cls) => '
' + body + '
'; const fm = financeMonthly || []; const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']; - const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流']; - const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.profit_annual||0, m.payment_annual||0, m.paid_annual||0, m.cashflow_annual||0]; - const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null]; + const LABELS = ['合同金额','确收金额','确收毛利','成本','已付']; + const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.paid_annual||0]; + const fmFields = ['sign','revenue','gross','cost','paid']; var thead = '指标年度累计'; for (var mi = 0; mi < 12; mi++) { thead += '' + mLbl[mi] + ''; } thead += ''; var tbody = ''; - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var rowHtml = '' + LABELS[ri] + ''; rowHtml += '' + moneyIntL(annualVals[ri]) + ''; for (var mi = 0; mi < 12; mi++) { var val = 0; if (fm[mi]) { - if (ri === 4) { val = (fm[mi].gross || 0) - (fm[mi].cost || 0); } - else if (ri === 7) { val = (fm[mi].payment || 0) - (fm[mi].paid || 0); } - else { var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; } + var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; } var cls = 'text-slate-700'; - if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; - if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; rowHtml += '' + moneyIntL(val) + ''; } tbody += rowHtml + ''; @@ -964,11 +959,10 @@ function renderFinanceOverview() { document.querySelector("#financeOverview").innerHTML = '
' + card('

业务(项目)财务月度概览

合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流

' + thead + '' + tbody + '
', 'p-4') + - '
' + + '
' + card('

月度签约趋势

2026
', 'p-4') + card('

月度确收与毛利

2026
', 'p-4') + card('

月度回款与已付

2026
', 'p-4') + - card('

月度项目利润

2026
', 'p-4') + '
'; if (window.lucide) window.lucide.createIcons(); @@ -985,21 +979,19 @@ function renderFinanceQuarterlyOverview() { const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN"); const card = (body, cls) => '
' + body + '
'; const fm = financeMonthly || []; - const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流']; + const LABELS = ['合同金额','确收金额','确收毛利','成本','已付']; const qLbl = ['Q1','Q2','Q3','Q4']; const qRanges = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]]; - const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.profit_annual||0, m.payment_annual||0, m.paid_annual||0, m.cashflow_annual||0]; - const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null]; - var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; + const annualVals = [m.signed_annual||m.signed_amount||0, m.revenue_annual||0, m.gross_annual||0, m.cost_annual||0, m.paid_annual||0]; + const fmFields = ['sign','revenue','gross','cost','paid']; + var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; for (var qi = 0; qi < 4; qi++) { - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var sum = 0; for (var mi = 0; mi < 3; mi++) { var idx = qRanges[qi][mi]; if (fm[idx]) { - if (ri === 4) { sum += (fm[idx].gross||0) - (fm[idx].cost||0); } - else if (ri === 7) { sum += (fm[idx].payment||0) - (fm[idx].paid||0); } - else { var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; } + var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; } } qData[ri][qi] = sum; @@ -1013,14 +1005,12 @@ function renderFinanceQuarterlyOverview() { thead += ''; var qoq = function(cur, prev) { if (!prev) return ''; var pct = Math.round((cur - prev) / prev * 100); var cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; var sign = pct >= 0 ? '+' : ''; return '' + sign + pct + '%'; }; var tbody = ''; - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var rowHtml = '' + LABELS[ri] + ''; rowHtml += '' + moneyIntL(annualVals[ri]) + ''; for (var qi = 0; qi < 4; qi++) { var val = qData[ri][qi]; var cls = 'text-slate-700'; - if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; - if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; rowHtml += '' + moneyIntL(val) + ''; if (qi > 0) { rowHtml += '' + qoq(val, qData[ri][qi - 1]) + ''; } } diff --git a/static/modules/plan.js b/static/modules/plan.js index 999c639..e93831e 100644 --- a/static/modules/plan.js +++ b/static/modules/plan.js @@ -1009,32 +1009,26 @@ function renderPlanOverview() { annualSums.payment += fm[i].payment; annualSums.paid += fm[i].paid; } const mLbl = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']; - const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流']; + const LABELS = ['合同金额','确收金额','确收毛利','成本','已付']; // 每行的年度累计 + 12个月值 const annualVals = [ - annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost, - annualSums.gross - annualSums.cost, annualSums.payment, annualSums.paid, - annualSums.payment - annualSums.paid, + annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost, annualSums.paid, ]; // 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid) - const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null]; + const fmFields = ['sign','revenue','gross','cost','paid']; var thead = '指标年度累计'; for (var mi = 0; mi < 12; mi++) { thead += '' + mLbl[mi] + ''; } thead += ''; var tbody = ''; - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var rowHtml = '' + LABELS[ri] + ''; rowHtml += '' + moneyIntL(annualVals[ri]) + ''; for (var mi = 0; mi < 12; mi++) { var val = 0; if (fm[mi]) { - if (ri === 4) { val = (fm[mi].gross || 0) - (fm[mi].cost || 0); } - else if (ri === 7) { val = (fm[mi].payment || 0) - (fm[mi].paid || 0); } - else { var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; } + var fk = fmFields[ri]; if (fk) val = fm[mi][fk] || 0; } var cls = 'text-slate-700'; - if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; - if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; rowHtml += '' + moneyIntL(val) + ''; } tbody += rowHtml + ''; @@ -1042,11 +1036,10 @@ function renderPlanOverview() { document.querySelector("#planOverview").innerHTML = '
' + card('

业务(项目)财务概览

合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流

' + thead + '' + tbody + '
', 'p-4') + - '
' + + '
' + card('

月度签约趋势

2026
', 'p-4') + card('

月度确收与毛利

2026
', 'p-4') + card('

月度回款与已付

2026
', 'p-4') + - card('

月度项目利润

2026
', 'p-4') + '
'; if (window.lucide) window.lucide.createIcons(); @@ -1062,7 +1055,7 @@ function renderPlanQuarterlyOverview() { const fm = computePlanMonthly(planFinances); const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN"); const card = (body, cls) => '
' + body + '
'; - const LABELS = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流']; + const LABELS = ['合同金额','确收金额','确收毛利','成本','已付']; const qLbl = ['Q1','Q2','Q3','Q4']; const qRanges = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]]; var annualSums2 = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 }; @@ -1071,20 +1064,18 @@ function renderPlanQuarterlyOverview() { annualSums2.gross += fm[i2].gross; annualSums2.cost += fm[i2].cost; annualSums2.payment += fm[i2].payment; annualSums2.paid += fm[i2].paid; } - const annualVals = [annualSums2.sign, annualSums2.revenue, annualSums2.gross, annualSums2.cost, annualSums2.gross - annualSums2.cost, annualSums2.payment, annualSums2.paid, annualSums2.payment - annualSums2.paid]; - const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null]; + const annualVals = [annualSums2.sign, annualSums2.revenue, annualSums2.gross, annualSums2.cost, annualSums2.paid]; + const fmFields = ['sign','revenue','gross','cost','paid']; // Calculate quarterly sums - var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; + var qData = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]; for (var qi = 0; qi < 4; qi++) { - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var sum = 0; for (var mi = 0; mi < 3; mi++) { var idx = qRanges[qi][mi]; if (fm[idx]) { - if (ri === 4) { sum += (fm[idx].gross||0) - (fm[idx].cost||0); } - else if (ri === 7) { sum += (fm[idx].payment||0) - (fm[idx].paid||0); } - else { var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; } + var fk = fmFields[ri]; if (fk) sum += fm[idx][fk] || 0; } } qData[ri][qi] = sum; @@ -1101,14 +1092,12 @@ function renderPlanQuarterlyOverview() { var qoq = function(cur, prev) { if (!prev) return ''; var pct = Math.round((cur - prev) / prev * 100); var cls = pct >= 0 ? 'text-green-600' : 'text-red-600'; var sign = pct >= 0 ? '+' : ''; return '' + sign + pct + '%'; }; var tbody = ''; - for (var ri = 0; ri < 8; ri++) { + for (var ri = 0; ri < 5; ri++) { var rowHtml = '' + LABELS[ri] + ''; rowHtml += '' + moneyIntL(annualVals[ri]) + ''; for (var qi = 0; qi < 4; qi++) { var val = qData[ri][qi]; var cls = 'text-slate-700'; - if (ri === 4) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; - if (ri === 7) cls = val >= 0 ? 'text-green-600' : 'text-red-600'; rowHtml += '' + moneyIntL(val) + ''; if (qi > 0) { rowHtml += '' + qoq(val, qData[ri][qi - 1]) + ''; @@ -1144,11 +1133,6 @@ function renderPlanCharts(data) { if (state.planChart3) state.planChart3.destroy(); state.planChart3 = new Chart(c3, Object.assign({}, iconf, { data: { labels: labels, datasets: [{ label: "回款", data: data.map(function(x) { return x.payment || 0; }), borderColor: "#d97706", backgroundColor: "rgba(217,119,6,0.06)", fill: true, tension: 0.3 }, { label: "已付", data: data.map(function(x) { return x.cost || 0; }), borderColor: "#7c3aed", backgroundColor: "rgba(124,58,237,0.06)", fill: true, tension: 0.3 }] } })); } - var c4 = document.querySelector("#planChartProfit"); - if (c4 && window.Chart) { - if (state.planChart4) state.planChart4.destroy(); - state.planChart4 = new Chart(c4, Object.assign({}, iconf, { data: { labels: labels, datasets: [{ label: "利润", data: data.map(function(x) { return x.gross || 0; }), borderColor: "#6366f1", backgroundColor: "rgba(99,102,241,0.06)", fill: true, tension: 0.3 }] } })); - } }; function planInlineSelect(name, pfId, field, currentVal, options) { diff --git a/templates/index.html b/templates/index.html index e3e50c2..17d9acf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -80,7 +80,7 @@
-

OPC Manager v1.0.0.29

+

OPC Manager v1.0.0.30

科普 OPC 工作台