diff --git a/static/modules/finance.js b/static/modules/finance.js
index d3c052f..ce556c2 100644
--- a/static/modules/finance.js
+++ b/static/modules/finance.js
@@ -253,18 +253,6 @@ function renderFinance() {
return `
`;
}
const isUnsigned = false;
- const METRIC_CARDS = [
- { label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' },
- { label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' },
- { label: '待回款', hideUnsigned: true, value: ctx => { const v = ctx.sumRev - ctx.sumPay; return { val: v < 0 ? '超额回款 ' + moneyWan(Math.abs(v)) : moneyWan(v), cls: v > 0 ? 'text-red-600' : v < 0 ? 'text-green-600' : 'text-slate-400' }; }, color: null },
- { label: '毛利', value: ctx => moneyWan(ctx.sumGross), color: 'text-green-700' },
- { label: '成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' },
- { label: '回款', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' },
- { label: '支出', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPaid), color: 'text-purple-700' },
- { label: '现金流(回款-支出)', hideUnsigned: true, value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null },
- { label: '毛利率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
- { label: '回款率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
- ].filter(c => true);
// 列排序(默认按确收金额从高到低)
if (!state.finSort) state.finSort = { key: 'sig|rev', asc: false };
@@ -308,22 +296,31 @@ function renderFinance() {
];
const renderView = (rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, emptyText) => {
- const ctx = { sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt };
- const cards = METRIC_CARDS.map(c => {
- const v = c.value(ctx);
- return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color];
- });
+ const receivable = sumRev - sumPay;
+ const payable = sumCost - sumPaid;
+ const cashflow = sumPay - sumPaid;
+ const netAsset = receivable - payable;
+ const m = moneyWan;
+ const rows_data = [
+ ['签约金额', m(signTotal), '合同签约总额'],
+ ['确收金额', m(sumRev), '已确认收入'],
+ ['毛利', m(sumGross), '确收毛利'],
+ ['应收', m(receivable), '确收−回款'],
+ ['应付', m(payable), '成本−支出'],
+ ['现金流', m(cashflow), '回款−支出'],
+ ['项目净资产', m(netAsset), '应收−应付'],
+ ];
+ const fs = '' + rows_data.map(r =>
+ '
').join('') + '
';
const thead = isUnsigned ? buildThead(UNSIGNED_COLS, 'uns') : buildThead(SIGNED_COLS, 'sig');
- const theadCols = isUnsigned ? 6 : 12;
+ const theadCols = isUnsigned ? 6 : 14;
const tbody = rows.length ? `${rows.join("")}` : `| ${emptyText} |
`;
-
- const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-5';
const subtitle = isUnsigned
? '合同 → 毛利 → 成本 → 项目利润'
- : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 支出 → 现金流 → 执行率 → 毛利率 → 回款率 → 付款率';
- return `${cards.map(([l, v, c]) => '
' + l + ' ' + v + '
').join("")}
` +
- `${isUnsigned ? '' : `${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` +
- card(`项目财务明细
${subtitle}
`, "p-4");
+ : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 支出 → 现金流';
+ return `${isUnsigned ? '' : `${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` +
+ card(fs +
+ `项目财务明细
${subtitle}
`, "p-4");
};
const fmtNoUnit = (v) => v ? `${Number(v||0).toLocaleString('zh-CN')}` : '—';
diff --git a/static/modules/plan.js b/static/modules/plan.js
index 2c269eb..f4f470e 100644
--- a/static/modules/plan.js
+++ b/static/modules/plan.js
@@ -252,18 +252,7 @@ function renderPlan() {
return ``;
}
const isUnsigned = false;
- const METRIC_CARDS = [
- { label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' },
- { label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' },
- { label: '待回款', hideUnsigned: true, value: ctx => { const v = ctx.sumRev - ctx.sumPay; return { val: v < 0 ? '超额回款 ' + moneyWan(Math.abs(v)) : moneyWan(v), cls: v > 0 ? 'text-red-600' : v < 0 ? 'text-green-600' : 'text-slate-400' }; }, color: null },
- { label: '毛利', value: ctx => moneyWan(ctx.sumGross), color: 'text-green-700' },
- { label: '当期成本(不含平台费用)', value: ctx => moneyWan(ctx.sumCost), color: 'text-rose-700' },
- { label: '回款', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPay), color: 'text-amber-700' },
- { label: '当期流出', hideUnsigned: true, value: ctx => moneyWan(ctx.sumPaid), color: 'text-purple-700' },
- { label: '现金流(回款-支出)', hideUnsigned: true, value: ctx => { const v = ctx.sumPay - ctx.sumPaid; return { val: moneyWan(v), cls: v >= 0 ? 'text-green-600' : 'text-red-600' }; }, color: null },
- { label: '毛利率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumGross ? Math.round(ctx.sumGross / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
- { label: '回款率', hideUnsigned: true, value: ctx => ctx.sumRev && ctx.sumPay ? Math.round(ctx.sumPay / ctx.sumRev * 100) + '%' : '—', color: 'text-slate-500' },
- ].filter(c => true);
+
// 列排序(默认按确收金额从高到低)
if (!state.planSort) state.planSort = { key: 'sig|rev', asc: false };
@@ -325,22 +314,31 @@ function renderPlan() {
];
const renderView = (rows, sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt, emptyText) => {
- const ctx = { sumRev, sumPay, sumCost, sumPaid, sumGross, signTotal, signCnt };
- const cards = METRIC_CARDS.map(c => {
- const v = c.value(ctx);
- return typeof v === 'object' ? [c.label, v.val, v.cls] : [c.label, v, c.color];
- });
+ const receivable = sumRev - sumPay;
+ const payable = sumCost - sumPaid;
+ const cashflow = sumPay - sumPaid;
+ const netAsset = receivable - payable;
+ const m = moneyWan;
+ const rows_data = [
+ ['签约金额', m(signTotal), '合同签约总额'],
+ ['确收金额', m(sumRev), '已确认收入'],
+ ['毛利', m(sumGross), '确收毛利'],
+ ['应收', m(receivable), '确收−回款'],
+ ['应付', m(payable), '成本−支出'],
+ ['现金流', m(cashflow), '回款−支出'],
+ ['项目净资产', m(netAsset), '应收−应付'],
+ ];
+ const fs = '' + rows_data.map(r =>
+ '
').join('') + '
';
const thead = isUnsigned ? buildThead(UNSIGNED_COLS, 'uns') : buildThead(SIGNED_COLS, 'sig');
- const theadCols = isUnsigned ? 9 : 13;
+ const theadCols = isUnsigned ? 9 : 14;
const tbody = rows.length ? `${rows.join("")}` : `| ${emptyText} |
`;
-
- const cardGrid = isUnsigned ? 'grid-cols-4' : 'grid-cols-5';
const subtitle = isUnsigned
? '合同 → 毛利 → 成本 → 项目利润'
- : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 已付 → 现金流';
- return `${cards.map(([l, v, c]) => '
' + l + ' ' + v + '
').join("")}
` +
- `${isUnsigned ? '' : `${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` +
- card(`项目财务明细
${subtitle}
`, "p-4");
+ : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 支出 → 现金流';
+ return `${isUnsigned ? '' : `${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` +
+ card(fs +
+ `项目财务明细
${subtitle}
`, "p-4");
};
const fmtNoUnit = (v) => v ? `${Number(v||0).toLocaleString('zh-CN')}` : '—';
diff --git a/templates/index.html b/templates/index.html
index c274136..9d4db26 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -84,7 +84,7 @@
-
OPC Manager v1.2.0.33
+
OPC Manager v1.2.0.34