// performance.js — 绩效模块 window.renderPerformance = () => { const data = state.data; if (!data) return; // 确保预算和已发生数据已加载 if (!data._perfDataLoaded) { loadPerfData().then(() => { data._perfDataLoaded = true; renderPerformance(); }); return; } // ── 状态初始化 ── if (state.planYear == null) state.planYear = new Date().getFullYear(); const planYear = state.planYear; if (!['annual','quarterly','monthly'].includes(state.perfView)) state.perfView = 'quarterly'; if (state.perfQuarter === undefined) state.perfQuarter = Math.floor(new Date().getMonth() / 3); const nowD = new Date(); const activeQ = state.perfQuarter; const qRanges = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]; const activeMonths = qRanges[activeQ]; const qLabels = ['Q1','Q2','Q3','Q4']; // 当前聚合范围 let aggMonths = []; if (state.perfView === 'annual') { for (var mi = 1; mi <= 12; mi++) aggMonths.push(mi); } else if (state.perfView === 'quarterly') { aggMonths = qRanges[activeQ]; } else if (state.perfView === 'monthly') { const m = state.perfMonth; aggMonths = m ? [parseInt(m.substring(5))] : []; } // 月度选择器默认值 if (state.perfView === 'monthly' && !state.perfMonth) { state.perfMonth = planYear + '-' + String(nowD.getMonth() + 1).padStart(2, '0'); } // ── 数据源 ── const pfs = (data.projectFinances || []); // 已发生 → 完成情况 const planPfs = (data.planFinances || []); // 预算 → 目标 // ── 通用聚合(支持年份过滤 + 月度范围)── function sumByMonths(items, field, useSign, months) { let total = 0; items.forEach(p => { if (useSign) { const sm = p.sign_month || ''; const smY = parseInt(sm.substring(0, 4)) || 0; const smM = parseInt(sm.substring(5)) || 0; if (!months.includes(smM)) return; if (planYear !== 0 && smY && smY !== planYear) return; total += parseFloat(p.sign_amount || 0); } else { let bd = []; try { bd = JSON.parse(p.budget_data || '[]'); } catch (e) {} bd.forEach(b => { const m = parseInt((b.month || '').substring(5)) || 0; const y = parseInt((b.month || '').substring(0, 4)) || 0; if (!months.includes(m)) return; if (planYear !== 0 && y && y !== planYear) return; total += parseFloat(b[field] || 0); }); } }); return Math.round(total); } // ── 计算 ── const signActual = sumByMonths(pfs, '', true, aggMonths); const revActual = sumByMonths(pfs, 'rev', false, aggMonths); const grossActual = sumByMonths(pfs, 'gross', false, aggMonths); const paymentActual = sumByMonths(pfs, 'payment', false, aggMonths); const signPlan = sumByMonths(planPfs, '', true, aggMonths); const revPlan = sumByMonths(planPfs, 'rev', false, aggMonths); const grossPlan = sumByMonths(planPfs, 'gross', false, aggMonths); const paymentPlan = sumByMonths(planPfs, 'payment', false, aggMonths); const rows = [ { key: 'sign', label: '签约', complete: signActual, plan: signPlan, icon: 'file-text', defWeight: 10 }, { key: 'rev', label: '收入', complete: revActual, plan: revPlan, icon: 'dollar-sign', defWeight: 20 }, { key: 'gross', label: '毛利', complete: grossActual, plan: grossPlan, icon: 'trending-up', defWeight: 50 }, { key: 'payment', label: '回款', complete: paymentActual, plan: paymentPlan, icon: 'coins', defWeight: 20 }, ]; // 存储 key 基于当前视图模式 let storageKey = 'opc-perf-t'; // default annual if (state.perfView === 'quarterly') storageKey = 'opc-perf-Q' + (activeQ + 1); else if (state.perfView === 'monthly') storageKey = 'opc-perf-M' + (state.perfMonth || ''); const saved = JSON.parse(localStorage.getItem(storageKey) || '{}'); function val(key, field, def) { const r = saved[key] || {}; return r[field] !== undefined && r[field] !== '' ? Number(r[field]) : def; } function moneyWan(v) { if (Math.abs(v) >= 10000) return (v / 10000).toFixed(1) + '万'; return v.toLocaleString(); } let totalScore = 0; const tableRows = rows.map(r => { const target = r.plan; const weight = val(r.key, 'weight', r.defWeight); const score = target > 0 ? Math.round((r.complete / target) * weight * 10) / 10 : 0; const rate = target > 0 ? Math.round((r.complete / target) * 100) : 0; totalScore += score; return ` ${r.label} ${moneyWan(target)} ${moneyWan(r.complete)} ${rate}%
${score.toFixed(1)} `; }).join(''); // ── Header: 仿预算/已发生模块的年/季/月筛选区 ── const yearOpts = [2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027]; const yearSelect = `年份:`; const viewBtns = ``; const quarterBtns = qLabels.map((ql, i) => `` ).join(''); const monthBtns = activeMonths.map(m => { const ms = planYear + '-' + String(m).padStart(2, '0'); return ``; }).join(''); const headerBar = `
${yearSelect}| ${viewBtns} |季度:${quarterBtns} |月度:${monthBtns}
`; document.querySelector("#performance").innerHTML = `
${headerBar}
${tableRows}
指标 目标(万) 完成情况 完成率 权重 评分
合计 100 ${totalScore.toFixed(1)}
`; if (window.lucide) window.lucide.createIcons(); }; // ── 筛选切换函数 ── window.setPerfYear = (y) => { state.planYear = parseInt(y); renderPerformance(); }; window.setPerfView = (v) => { state.perfView = v; renderPerformance(); }; window.setPerfQuarter = (q) => { state.perfView = 'quarterly'; state.perfQuarter = q; renderPerformance(); }; window.setPerfMonth = (m) => { state.perfView = 'monthly'; state.perfMonth = m; renderPerformance(); }; // ── 编辑保存 ── window.updatePerfScore = (key, field, value) => { let storageKey = 'opc-perf-t'; if (state.perfView === 'quarterly') storageKey = 'opc-perf-Q' + ((state.perfQuarter || 0) + 1); else if (state.perfView === 'monthly') storageKey = 'opc-perf-M' + (state.perfMonth || ''); const saved = JSON.parse(localStorage.getItem(storageKey) || '{}'); saved[key] = saved[key] || {}; saved[key][field] = value; localStorage.setItem(storageKey, JSON.stringify(saved)); renderPerformance(); }; // ── 数据加载 ── async function loadPerfData() { for (var i = 0; i < 2; i++) { var r = i === 0 ? 'planFinances' : 'projectFinances'; try { state.data[r] = await api("/api/" + r + "/list?tenant=" + encodeURIComponent(state.tenant)); } catch(e) { console.error('loadPerfData failed:', r, e.message); } } }