Files
amazon-za-delivery/index.html
T

46 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Amazon ZA 每日交付报告</title>
<style>
:root{--bg:#f4f7fb;--panel:#fff;--ink:#162033;--muted:#667085;--line:#dce3ec;--blue:#2f6fed;--gold:#d79b24;--orange:#e56d35;--soft:#eaf1ff;--shadow:0 8px 28px rgba(23,43,77,.08)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}main{max-width:1180px;margin:auto;padding:42px 24px 64px}.eyebrow{color:var(--blue);font-weight:700;letter-spacing:.08em;text-transform:uppercase}.hero h1{font-size:clamp(28px,4vw,46px);line-height:1.1;margin:8px 0 12px}.hero p{color:var(--muted);font-size:16px;margin:0;max-width:800px}.status{display:inline-flex;align-items:center;gap:8px;margin-top:18px;padding:7px 11px;border:1px solid var(--line);border-radius:999px;background:var(--panel);color:var(--muted)}.dot{width:8px;height:8px;border-radius:50%;background:var(--blue)}section{margin-top:30px}h2{font-size:22px;margin:0 0 12px}.summary{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.card,.panel{background:var(--panel);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow)}.card{padding:18px}.card .label{color:var(--muted);font-weight:600}.card .value{font-size:28px;font-weight:750;margin-top:6px;font-variant-numeric:tabular-nums}.card .note{color:var(--muted);font-size:12px;margin-top:3px}.panel{padding:22px;margin-top:14px}.takeaway{border-left:4px solid var(--blue);padding-left:14px;color:var(--muted)}.takeaway strong{color:var(--ink)}.charts{display:grid;grid-template-columns:1fr 1fr;gap:14px}.chart{min-height:300px;overflow-x:auto}.chart-head{margin-bottom:16px}.chart-title{font-weight:750;font-size:16px}.chart-sub{color:var(--muted);font-size:12px}.bars{height:220px;display:flex;align-items:end;gap:14px;padding:20px 10px 28px;border-bottom:1px solid var(--line);position:relative}.bar-group{flex:1;height:100%;display:flex;align-items:end;justify-content:center;gap:6px;position:relative}.bar{min-width:8px;max-width:34px;width:33%;border-radius:6px 6px 0 0;position:relative}.bar span{position:absolute;top:-21px;left:50%;transform:translateX(-50%);font-size:11px;font-weight:700;white-space:nowrap}.bar-label{position:absolute;bottom:-25px;left:50%;transform:translateX(-50%);color:var(--muted);font-size:11px}.legend{display:flex;gap:14px;margin-top:13px;color:var(--muted);font-size:12px}.swatch{width:9px;height:9px;border-radius:2px;display:inline-block;margin-right:5px}.rate-bars .bar{width:52%;background:var(--orange)}.progress{height:14px;background:var(--soft);border-radius:999px;overflow:hidden;margin:18px 0 8px}.progress-fill{height:100%;background:var(--blue);border-radius:inherit}.progress-labels{display:flex;justify-content:space-between;color:var(--muted);font-size:12px}table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}th,td{padding:11px 9px;border-bottom:1px solid var(--line);text-align:right;white-space:nowrap}th:first-child,td:first-child{text-align:left}th{color:var(--muted);font-size:12px}tbody tr:hover{background:var(--soft)}tr.future{color:var(--muted);opacity:.65}.footer-note{color:var(--muted);font-size:12px;margin-top:18px}.error{padding:18px;border:1px solid #f0b7b7;background:#fff2f2;color:#912b2b;border-radius:12px}.login-shell{min-height:100vh;display:grid;place-items:center;padding:24px}.login-card{width:min(430px,100%);background:var(--panel);border:1px solid var(--line);border-radius:22px;padding:30px;box-shadow:var(--shadow)}.login-card h1{font-size:28px;margin:8px 0}.login-card p{color:var(--muted);margin:0 0 22px}.field{display:block;margin-top:14px;font-weight:700}.field input{display:block;width:100%;margin-top:7px;border:1px solid var(--line);border-radius:11px;padding:12px 13px;background:var(--bg);color:var(--ink);font:inherit;outline:none}.field input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(47,111,237,.14)}button{width:100%;margin-top:20px;border:0;border-radius:11px;padding:13px;background:var(--blue);color:white;font:inherit;font-weight:750;cursor:pointer}button:disabled{opacity:.65;cursor:wait}.login-error{min-height:22px;color:#c13a3a;font-size:13px;margin-top:10px}.access-note{font-size:12px!important;margin-top:18px!important}#report[hidden],#login[hidden]{display:none!important}@media(max-width:850px){.summary{grid-template-columns:1fr 1fr}.charts{grid-template-columns:1fr}}@media(max-width:520px){main{padding:26px 14px}.summary{grid-template-columns:1fr}.panel{padding:16px;overflow:auto}.card .value{font-size:24px}.login-card{padding:24px}}
@media(prefers-color-scheme:dark){:root{--bg:#0c1220;--panel:#121a2a;--ink:#eef3fb;--muted:#9ba9bd;--line:#28354a;--soft:#182743;--shadow:none}}
</style>
</head>
<body><div id="login" class="login-shell"><form id="loginForm" class="login-card" autocomplete="on"><div class="eyebrow">Restricted Report</div><h1>登录查看报告</h1><p>请输入获授权的邮箱与密码。</p><label class="field">邮箱<input id="email" type="email" autocomplete="username" required></label><label class="field">密码<input id="password" type="password" autocomplete="current-password" required></label><button id="loginButton" type="submit">登录</button><div id="loginError" class="login-error" role="alert" aria-live="polite"></div><p class="access-note">仅授权名单内的邮箱可以访问。</p></form></div><main id="report" hidden>
<header class="hero"><div class="eyebrow">Daily Delivery · ZA</div><h1>Amazon ZA 每日交付报告</h1><p>包名:com.amazon.mShop.android.shopping。聚焦 repostback 交付、E1+E2 转化与 Midas / Payable spend。</p><div class="status"><span class="dot"></span><span id="range">正在载入数据…</span></div></header>
<section><h2>Executive Summary</h2><div id="summaryText" class="panel takeaway">载入中…</div></section>
<section><h2>核心指标</h2><div class="summary" id="cards"></div></section>
<section><h2>8 月预算消耗</h2><div id="budgetText" class="takeaway"></div><div class="summary" id="budgetCards" style="margin-top:14px"></div><div class="panel"><div class="chart-title">月度预算使用进度</div><div class="chart-sub">预算按 E1+E2 × 49.50 ZAR 扣减</div><div class="progress"><div id="budgetProgress" class="progress-fill"></div></div><div class="progress-labels"><span id="budgetUsed"></span><span id="budgetTotal"></span></div></div></section>
<section><h2>每日交付与转化</h2><div class="takeaway"><strong>如何阅读:</strong>柱状图展示本月截至数据日的逐日 repostback 交付;结合 E1+E2% 判断每日转化效率,整月完整日期则保留在下方明细表。</div><div class="charts"><div class="panel chart"><div class="chart-head"><div class="chart-title">每日 Install、Event1 与 Event2</div><div class="chart-sub">均取 repostback;按 ZA 日期汇总</div></div><div id="deliveryChart" class="bars"></div><div class="legend"><span><i class="swatch" style="background:var(--blue)"></i>Install</span><span><i class="swatch" style="background:var(--gold)"></i>Event1</span><span><i class="swatch" style="background:var(--orange)"></i>Event2</span></div></div><div class="panel chart"><div class="chart-head"><div class="chart-title">每日 E1+E2%</div><div class="chart-sub">(Event1 + Event2) ÷ Install</div></div><div id="rateChart" class="bars rate-bars"></div></div></div></section>
<section><h2>消耗对比</h2><div id="spendText" class="takeaway"></div><div class="panel chart"><div class="chart-head"><div class="chart-title">Midas spend 与 Payable spend</div><div class="chart-sub">USD;Payable spend = (E1 + E2) × $3</div></div><div id="spendChart" class="bars"></div><div class="legend"><span><i class="swatch" style="background:var(--blue)"></i>Midas spend</span><span><i class="swatch" style="background:var(--gold)"></i>Payable spend</span></div></div></section>
<section><h2>8 月每日明细</h2><div class="panel" style="overflow-x:auto"><table><thead><tr><th>日期</th><th>Install</th><th>Event1</th><th>Event2</th><th>Total</th><th>Event rate</th><th>Payable (ZAR)</th><th>日预算</th><th>FFR</th><th>Midas (USD)</th><th>eCPI</th></tr></thead><tbody id="rows"></tbody></table><div class="footer-note">口径:Install、Event1、Event2 均取 repostback;Total = E1+E2;FFR = Total ÷ Daily cap。灰色日期为数据截止日之后的剩余月份。</div></div></section>
<section><h2>建议关注</h2><div class="panel"><ul><li>优先关注 FFR 明显低于 100% 的日期,判断是流量不足、事件率下降还是日 cap 执行问题。</li><li>剩余预算需要的日均消耗已高于当前日预算;如目标是完整消耗月预算,需要确认是否提高每日 cap。</li><li>对比 Payable spend 与 Midas spend 的日级差额,排查差额扩大日期的事件质量和计费口径。</li></ul></div></section>
</main><script>
const fmt=n=>new Intl.NumberFormat('en-US').format(n), money=n=>'$'+new Intl.NumberFormat('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}).format(n), pct=n=>n==null?'—':n.toFixed(2)+'%';
const label=d=>d.slice(5).replace('-','/');
function bars(el,data,series,max){const showValues=data.length<=12;el.style.minWidth=Math.max(0,data.length*48)+'px';el.innerHTML=data.map(d=>`<div class="bar-group">${series.map(s=>`<div class="bar" style="height:${Math.max(2,d[s.key]/max*100)}%;background:${s.color}">${showValues?`<span>${s.format(d[s.key])}</span>`:''}</div>`).join('')}<div class="bar-label">${label(d.date)}</div></div>`).join('')}
const ACCESS={emails:['a91dc567bcdb6d054cf121d7e4016454ee3d316e3b452b851ae4d3087bc99bb2'],salt:'amazon-za-delivery-2026-08',password:'2344c6c9cf72430cafbf48e014d7f721a53484f766f1455f0c12232515ec61e9'};
async function sha256(value){const bytes=new TextEncoder().encode(value);const digest=await crypto.subtle.digest('SHA-256',bytes);return [...new Uint8Array(digest)].map(x=>x.toString(16).padStart(2,'0')).join('')}
function showReport(){document.getElementById('login').hidden=true;document.getElementById('report').hidden=false;loadReport()}
async function loadReport(){fetch('data.json',{cache:'no-store'}).then(r=>{if(!r.ok)throw Error('data.json '+r.status);return r.json()}).then(data=>{
const t=data.totals,b=data.budget,all=data.daily,d=all.filter(x=>x.is_actual),latest=d[d.length-1],first=d[0];document.getElementById('range').textContent=`2026-08-01 至 2026-08-31 · 数据截至 ${data.meta.data_cutoff}`;
const delta=first&&latest?latest.e1e2_rate-first.e1e2_rate:0;document.getElementById('summaryText').innerHTML=`<strong>截至 ${data.meta.data_cutoff},累计 ${fmt(t.install)} Install,产生 ${fmt(t.e1e2)} 个 E1+E2,整体转化率 ${pct(t.e1e2_rate)}。</strong> 已消耗月预算 ${pct(b.used_rate)},当前消耗进度相当于同期预算的 ${pct(b.pacing_rate)}。按当前日均速度推算,月底预计${b.projected_month_variance_zar>=0?'结余':'超支'} ZAR ${fmt(Math.abs(b.projected_month_variance_zar))}。`;
const cards=[['Install',fmt(t.install),'af_app_opened repostback'],['E1 + E2',fmt(t.e1e2),`${fmt(t.event1)} E1 · ${fmt(t.event2)} E2`],['E1+E2%',pct(t.e1e2_rate),'累计 E1+E2 ÷ 累计 Install'],['Payable spend',money(t.payable_spend),`Midas spend ${money(t.midas_spend)}`]];document.getElementById('cards').innerHTML=cards.map(x=>`<div class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="note">${x[2]}</div></div>`).join('');
const budgetCards=[['月度预算','R '+fmt(b.monthly_budget_zar),money(b.monthly_budget_usd)],['已用预算','R '+fmt(b.used_budget_zar),pct(b.used_rate)+' of month'],['剩余预算','R '+fmt(b.remaining_budget_zar),money(b.remaining_budget_usd)],['剩余日均需求','R '+fmt(b.required_daily_budget_zar),`${fmt(b.required_daily_events)} E1+E2 / day`]];document.getElementById('budgetCards').innerHTML=budgetCards.map(x=>`<div class="card"><div class="label">${x[0]}</div><div class="value">${x[1]}</div><div class="note">${x[2]}</div></div>`).join('');
document.getElementById('budgetText').innerHTML=`<strong>预算消耗低于截至当前日期的均匀进度。</strong> 已用 R ${fmt(b.used_budget_zar)},同期预算应为 R ${fmt(b.expected_budget_to_date_zar)};剩余 ${b.remaining_days} 天需要日均 R ${fmt(b.required_daily_budget_zar)},高于当前日预算 R ${fmt(b.daily_budget_zar)}。`;
document.getElementById('budgetProgress').style.width=Math.min(b.used_rate,100)+'%';document.getElementById('budgetUsed').textContent=`已用 ${pct(b.used_rate)} · R ${fmt(b.used_budget_zar)}`;document.getElementById('budgetTotal').textContent=`总预算 R ${fmt(b.monthly_budget_zar)}`;
bars(document.getElementById('deliveryChart'),d,[{key:'install',color:'var(--blue)',format:fmt},{key:'event1',color:'var(--gold)',format:fmt},{key:'event2',color:'var(--orange)',format:fmt}],Math.max(...d.flatMap(x=>[x.install,x.event1,x.event2]),1));
bars(document.getElementById('rateChart'),d,[{key:'e1e2_rate',color:'var(--orange)',format:pct}],Math.max(...d.map(x=>x.e1e2_rate||0),1));
bars(document.getElementById('spendChart'),d,[{key:'midas_spend',color:'var(--blue)',format:money},{key:'payable_spend',color:'var(--gold)',format:money}],Math.max(...d.flatMap(x=>[x.midas_spend,x.payable_spend]),1));
const gap=t.payable_spend-t.midas_spend;document.getElementById('spendText').innerHTML=`<strong>累计 Payable spend ${gap>=0?'高于':'低于'} Midas spend ${money(Math.abs(gap))}。</strong> 该差额来自约定的事件计费公式与平台实际消耗之间的口径差异,不应直接解读为利润。`;
document.getElementById('rows').innerHTML=all.map(x=>`<tr class="${x.is_actual?'':'future'}"><td>${x.date}</td><td>${fmt(x.install)}</td><td>${fmt(x.event1)}</td><td>${fmt(x.event2)}</td><td>${fmt(x.e1e2)}</td><td>${pct(x.e1e2_rate)}</td><td>R ${fmt(x.payable_spend_zar)}</td><td>R ${fmt(x.daily_budget_zar)}</td><td>${pct(x.ffr_rate)}</td><td>${money(x.midas_spend)}</td><td>${x.ecpi_usd==null?'—':money(x.ecpi_usd)}</td></tr>`).join('');
}).catch(e=>{document.getElementById('summaryText').className='error';document.getElementById('summaryText').textContent='数据载入失败:'+e.message})}
document.getElementById('loginForm').addEventListener('submit',async e=>{e.preventDefault();const button=document.getElementById('loginButton'),error=document.getElementById('loginError');button.disabled=true;error.textContent='';try{const email=document.getElementById('email').value.trim().toLowerCase(),password=document.getElementById('password').value;const [emailHash,passwordHash]=await Promise.all([sha256(email),sha256(ACCESS.salt+':'+password)]);if(ACCESS.emails.includes(emailHash)&&passwordHash===ACCESS.password){sessionStorage.setItem('amazonZaAccess','granted');showReport()}else{error.textContent='邮箱或密码不正确。'}}catch(_){error.textContent='当前浏览器无法完成验证,请更换现代浏览器。'}finally{button.disabled=false}});
if(sessionStorage.getItem('amazonZaAccess')==='granted')showReport();
</script></body></html>