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 => + '
' + r[0] + '
' + r[1] + '
').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}

${thead}${tbody}
`, "p-4"); + : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 支出 → 现金流'; + return `${isUnsigned ? '' : `
${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` + + card(fs + + `

项目财务明细

${subtitle}

${thead}${tbody}
`, "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 => + '
' + r[0] + '
' + r[1] + '
').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}

${thead}${tbody}
`, "p-4"); + : '合同 → 确收 → 毛利 → 成本 → 项目利润 → 回款 → 支出 → 现金流'; + return `${isUnsigned ? '' : `
${card(`
${finHeaderBase}
${finAddBtn}
`, "py-2 px-4")}
`}` + + card(fs + + `

项目财务明细

${subtitle}

${thead}${tbody}
`, "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

科普 OPC 工作台