151 lines
7.6 KiB
HTML
151 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>成本周报 - 0817-0823</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f7fa; color: #333; padding: 20px; }
|
|
h1 { text-align: center; margin-bottom: 8px; font-size: 24px; }
|
|
.subtitle { text-align: center; color: #666; margin-bottom: 24px; font-size: 14px; }
|
|
.kpi-row { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 24px; }
|
|
.kpi { background: #fff; border-radius: 8px; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); text-align: center; min-width: 160px; }
|
|
.kpi .label { font-size: 12px; color: #888; margin-bottom: 4px; }
|
|
.kpi .value { font-size: 22px; font-weight: 700; }
|
|
.kpi .wow { font-size: 12px; margin-top: 4px; }
|
|
.up { color: #e53e3e; }
|
|
.down { color: #38a169; }
|
|
.section { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); margin-bottom: 24px; overflow: hidden; }
|
|
.section h2 { padding: 14px 20px; font-size: 16px; border-bottom: 1px solid #eee; background: #fafbfc; }
|
|
.tab-bar { display: flex; border-bottom: 1px solid #eee; background: #fafbfc; }
|
|
.tab { padding: 10px 20px; cursor: pointer; font-size: 14px; border-bottom: 2px solid transparent; }
|
|
.tab.active { border-bottom-color: #4472c4; color: #4472c4; font-weight: 600; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
th { background: #4472c4; color: #fff; padding: 8px 10px; text-align: right; white-space: nowrap; position: sticky; top: 0; }
|
|
th:first-child, th:nth-child(2) { text-align: left; }
|
|
td { padding: 7px 10px; border-bottom: 1px solid #f0f0f0; text-align: right; white-space: nowrap; }
|
|
td:first-child, td:nth-child(2) { text-align: left; }
|
|
tr:hover td { background: #f8f9ff; }
|
|
tr.total td { font-weight: 700; background: #f5f5f5; border-top: 2px solid #ddd; }
|
|
.neg { color: #e53e3e; }
|
|
.pos { color: #38a169; }
|
|
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; }
|
|
.badge-red { background: #fee; color: #c00; }
|
|
.badge-orange { background: #fff3e0; color: #e65100; }
|
|
.table-wrap { max-height: 600px; overflow: auto; }
|
|
.focus-reason { font-size: 11px; color: #666; max-width: 200px; white-space: normal; }
|
|
@media (max-width: 768px) {
|
|
.kpi-row { gap: 8px; }
|
|
.kpi { min-width: 120px; padding: 12px; }
|
|
.kpi .value { font-size: 18px; }
|
|
table { font-size: 12px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>成本周报</h1>
|
|
<div class="subtitle" id="subtitle">加载中...</div>
|
|
<div class="kpi-row" id="kpis"></div>
|
|
|
|
<div class="section">
|
|
<h2>总表 - 按业务收支线</h2>
|
|
<div class="table-wrap"><table id="summary-table"></table></div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="tab-bar">
|
|
<div class="tab active" onclick="showTab('details')">详情</div>
|
|
<div class="tab" onclick="showTab('focus')">重点关注 (<span id="focus-count">0</span>)</div>
|
|
</div>
|
|
<div id="tab-details" class="table-wrap" style="max-height:700px;"><table id="details-table"></table></div>
|
|
<div id="tab-focus" class="table-wrap" style="display:none;max-height:700px;"><table id="focus-table"></table></div>
|
|
</div>
|
|
|
|
<script>
|
|
const fmt = (v, type) => {
|
|
if (v === null || v === undefined) return '-';
|
|
if (type === 'pct') return (v * 100).toFixed(1) + '%';
|
|
if (type === 'money') return '$' + v.toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 0});
|
|
return v.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
|
};
|
|
const wowClass = (v, invert) => {
|
|
if (v === null || v === undefined) return '';
|
|
if (invert) return v > 0 ? 'up' : v < 0 ? 'down' : '';
|
|
return v > 0 ? 'pos' : v < 0 ? 'neg' : '';
|
|
};
|
|
const wowStr = (v) => {
|
|
if (v === null || v === undefined) return '-';
|
|
return (v >= 0 ? '+' : '') + (v * 100).toFixed(1) + '%';
|
|
};
|
|
|
|
function showTab(name) {
|
|
document.getElementById('tab-details').style.display = name === 'details' ? '' : 'none';
|
|
document.getElementById('tab-focus').style.display = name === 'focus' ? '' : 'none';
|
|
document.querySelectorAll('.tab').forEach((t, i) => t.classList.toggle('active', i === (name === 'details' ? 0 : 1)));
|
|
}
|
|
|
|
fetch('data.json').then(r => r.json()).then(data => {
|
|
document.getElementById('subtitle').textContent = `${data.week} | 生成于 ${data.generated}`;
|
|
|
|
// KPIs from 合计 row
|
|
const total = data.summary.find(r => r.biz === '合计') || {};
|
|
const kpis = [
|
|
{ label: '本周Revenue', value: fmt(total.tw_rev, 'money'), wow: total.rev_wow, wowLabel: '环比' },
|
|
{ label: '本周Cost', value: fmt(total.tw_cost, 'money'), wow: total.cost_wow, wowLabel: '环比', invert: true },
|
|
{ label: '本周毛利', value: fmt(total.tw_profit, 'money'), wow: total.profit_wow, wowLabel: '环比' },
|
|
{ label: '毛利率', value: fmt(total.tw_gpr, 'pct'), wow: total.gpr_diff, wowLabel: '变化' },
|
|
];
|
|
document.getElementById('kpis').innerHTML = kpis.map(k => `
|
|
<div class="kpi">
|
|
<div class="label">${k.label}</div>
|
|
<div class="value">${k.value}</div>
|
|
<div class="wow ${wowClass(k.wow, k.invert)}">${k.wowLabel}: ${wowStr(k.wow)}</div>
|
|
</div>`).join('');
|
|
|
|
// Summary table
|
|
const sRows = data.summary.filter(r => r.biz !== '合计');
|
|
document.getElementById('summary-table').innerHTML = `
|
|
<tr><th>业务收支线</th><th>本周Revenue</th><th>本周Cost</th><th>本周毛利</th><th>毛利率</th><th>Rev环比</th><th>Cost环比</th><th>Rev差额</th></tr>
|
|
${sRows.map(r => `<tr>
|
|
<td>${r.biz}</td><td>${fmt(r.tw_rev,'money')}</td><td>${fmt(r.tw_cost,'money')}</td>
|
|
<td>${fmt(r.tw_profit,'money')}</td><td>${fmt(r.tw_gpr,'pct')}</td>
|
|
<td class="${wowClass(r.rev_wow)}">${wowStr(r.rev_wow)}</td>
|
|
<td class="${wowClass(r.cost_wow, true)}">${wowStr(r.cost_wow)}</td>
|
|
<td>${fmt(r.rev_diff,'money')}</td>
|
|
</tr>`).join('')}
|
|
<tr class="total"><td>合计</td><td>${fmt(total.tw_rev,'money')}</td><td>${fmt(total.tw_cost,'money')}</td>
|
|
<td>${fmt(total.tw_profit,'money')}</td><td>${fmt(total.tw_gpr,'pct')}</td>
|
|
<td class="${wowClass(total.rev_wow)}">${wowStr(total.rev_wow)}</td>
|
|
<td class="${wowClass(total.cost_wow, true)}">${wowStr(total.cost_wow)}</td>
|
|
<td>${fmt(total.rev_diff,'money')}</td></tr>`;
|
|
|
|
// Details table
|
|
document.getElementById('details-table').innerHTML = `
|
|
<tr><th>Channel</th><th>业务线</th><th>本周Rev</th><th>本周Cost</th><th>毛利</th><th>毛利率</th><th>Rev环比</th><th>Cost环比</th><th>Rev差额</th></tr>
|
|
${data.details.map(r => `<tr>
|
|
<td>${r.channel}</td><td>${r.biz}</td>
|
|
<td>${fmt(r.tw_rev,'money')}</td><td>${fmt(r.tw_cost,'money')}</td>
|
|
<td>${fmt(r.tw_profit,'money')}</td>
|
|
<td class="${r.tw_gpr < 0 ? 'neg' : ''}">${fmt(r.tw_gpr,'pct')}</td>
|
|
<td class="${wowClass(r.rev_wow)}">${wowStr(r.rev_wow)}</td>
|
|
<td class="${wowClass(r.cost_wow, true)}">${wowStr(r.cost_wow)}</td>
|
|
<td>${fmt(r.rev_diff,'money')}</td>
|
|
</tr>`).join('')}`;
|
|
|
|
// Focus table
|
|
document.getElementById('focus-count').textContent = data.focus.length;
|
|
document.getElementById('focus-table').innerHTML = `
|
|
<tr><th>Channel</th><th>业务线</th><th>本周Rev</th><th>本周Cost</th><th>毛利</th><th>毛利率</th><th>关注原因</th></tr>
|
|
${data.focus.map(r => `<tr>
|
|
<td>${r.channel}</td><td>${r.biz}</td>
|
|
<td>${fmt(r.tw_rev,'money')}</td><td>${fmt(r.tw_cost,'money')}</td>
|
|
<td>${fmt(r.tw_profit,'money')}</td>
|
|
<td class="${r.tw_gpr < 0 ? 'neg' : ''}">${fmt(r.tw_gpr,'pct')}</td>
|
|
<td class="focus-reason">${r.reason}</td>
|
|
</tr>`).join('')}`;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|