Merge branch 'dev'
All checks were successful
Deploy / deploy (push) Successful in 12s

This commit is contained in:
mac
2026-07-08 16:05:45 +08:00
3 changed files with 52 additions and 24 deletions

View File

@@ -286,14 +286,14 @@ function renderFinance() {
return h;
};
const SIGNED_COLS = [
{ key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'rev', label: '已确收' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' },
{ key: 'profit', 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: '付款率' },
];
const UNSIGNED_COLS = [
{ key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'client_name', label: '客户' }, { key: 'customer_name', label: '项目名称' }, { key: 'sign_amount', label: '签约金额' },
{ key: 'sign_month', label: '签约月份' }, { key: 'gross', label: '毛利' }, { key: 'cost', label: '成本' },
{ key: 'profit', label: '项目利润' },
];
@@ -305,7 +305,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 ? 7 : 14;
const tbody = rows.length ? `<tbody>${rows.join("")}</tbody>` : `<tr><td colspan="${theadCols}" class="p-6 text-center text-slate-400">${emptyText}</td></tr>`;
const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-6';

View File

@@ -965,11 +965,35 @@ window.deletePlanItem = async () => {
}
}
// 从 planFinances 计算月度数据
function computePlanMonthly(planFinances) {
var pfs = planFinances || [];
var months = [];
var year = new Date().getFullYear();
for (var mi = 1; mi <= 12; mi++) { months.push(year + '-' + String(mi).padStart(2, '0')); }
var data = [];
for (var i = 0; i < 12; i++) {
var month = months[i];
var sign = 0, revenue = 0, gross = 0, payment = 0, cost = 0, paid = 0;
for (var j = 0; j < pfs.length; j++) {
var pf = pfs[j];
if ((pf.sign_month || '') === month) sign += parseFloat(pf.sign_amount || 0);
var bd = []; try { bd = JSON.parse(pf.budget_data || '[]'); } catch(e) {}
var b = bd.find(function(x) { return (x.month || '') === month; });
if (b) { revenue += parseFloat(b.rev || 0); gross += parseFloat(b.gross || 0); payment += parseFloat(b.payment || 0); }
var ed = []; try { ed = JSON.parse(pf.expense_data || '[]'); } catch(e) {}
var e = ed.find(function(x) { return (x.month || '') === month; });
if (e) { cost += parseFloat(e.cost || 0); paid += parseFloat(e.paid || 0); }
}
data.push({ month: month, sign: Math.round(sign), revenue: Math.round(revenue), gross: Math.round(gross), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid) });
}
return data;
}
// 总览渲染
function renderPlanOverview() {
const { summary, financeMonthly } = state.data;
if (!summary) return;
const m = summary.metrics;
const planFinances = state.data.planFinances || [];
const fm = computePlanMonthly(planFinances);
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const moneyWan = (v) => {
const n = Number(v || 0);
@@ -977,20 +1001,20 @@ function renderPlanOverview() {
};
const card = (body, cls) => '<div class="card ' + (cls || 'p-4') + '">' + body + '</div>';
// 月度数据 (financeMonthly 有12个月)
const fm = financeMonthly || [];
// 从 planFinances 计算年度累计
var annualSums = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 };
for (var i = 0; i < fm.length; i++) {
annualSums.sign += fm[i].sign; annualSums.revenue += fm[i].revenue;
annualSums.gross += fm[i].gross; annualSums.cost += fm[i].cost;
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 = ['合同金额','确收金额','确收毛利','成本','项目利润','回款金额','已付','现金流'];
// 每行的年度累计 + 12个月值
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,
annualSums.sign, annualSums.revenue, annualSums.gross, annualSums.cost,
annualSums.gross - annualSums.cost, annualSums.payment, annualSums.paid,
annualSums.payment - annualSums.paid,
];
// 字段映射: sign, revenue, gross, cost, profit(=gross-cost), payment, paid, cashflow(=payment-paid)
const fmFields = ['sign','revenue','gross','cost',null,'payment','paid',null];
@@ -1027,23 +1051,27 @@ function renderPlanOverview() {
if (window.lucide) window.lucide.createIcons();
// 渲染图表(复用 home.js 的 moneyTick / chartOptions / monthLabels
if (financeMonthly && window.Chart) {
renderPlanCharts(financeMonthly);
if (fm && window.Chart) {
renderPlanCharts(fm);
}
}
function renderPlanQuarterlyOverview() {
const { summary, financeMonthly } = state.data;
if (!summary) return;
const m = summary.metrics;
const planFinances = state.data.planFinances || [];
const fm = computePlanMonthly(planFinances);
const moneyIntL = (v) => Math.round(Number(v || 0)).toLocaleString("zh-CN");
const card = (body, cls) => '<div class="card ' + (cls || 'p-4') + '">' + body + '</div>';
const fm = financeMonthly || [];
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];
var annualSums2 = { sign: 0, revenue: 0, gross: 0, cost: 0, payment: 0, paid: 0 };
for (var i2 = 0; i2 < fm.length; i2++) {
annualSums2.sign += fm[i2].sign; annualSums2.revenue += fm[i2].revenue;
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];
// Calculate quarterly sums

View File

@@ -80,7 +80,7 @@
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
<div class="flex items-center gap-3 w-full">
<div class="flex-1">
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.27</span></p>
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.28</span></p>
<div class="flex items-center gap-4 mt-1">
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">