21 lines
4.6 KiB
HTML
Executable File
21 lines
4.6 KiB
HTML
Executable File
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Click Gap 数据看板</title>
|
|
<style>body{font:14px system-ui,sans-serif;margin:2rem;color:#223}label{margin-right:1rem}input,select,button{padding:.35rem}table{border-collapse:collapse;width:100%;margin-top:1rem}th,td{border:1px solid #ccd;padding:.45rem;text-align:right}th{cursor:pointer;background:#eef}th:first-child,td:first-child,th:nth-child(2),td:nth-child(2),th:nth-child(3),td:nth-child(3){text-align:left}.status{padding:.7rem;background:#f5f5f5}.error{color:#a00}.sort{font-size:.8em}</style>
|
|
</head>
|
|
<body><h1>Click Gap 数据看板</h1><div><label>开始 <input id="start" type="date"></label><label>结束 <input id="end" type="date"></label><label>pkg_name <select id="pkg"><option value="">全部</option></select></label><button id="apply">查询</button><button id="reset">重置</button></div><p id="status" class="status">加载中…</p><table><thead><tr id="head"></tr></thead><tbody id="body"></tbody></table>
|
|
<script>
|
|
(() => {"use strict";
|
|
const columns=["dt","attr_platform","pkg_name","shareit_clicks","af_clicks","installs_ua","clicks_diff","clicks_diff_rate"];let rows=[], failed=[];let sort={key:"dt",direction:-1};
|
|
const $=id=>document.getElementById(id), status=$('status');
|
|
function text(value){return value===null||value===undefined?"—":String(value)}
|
|
function validDate(v){return /^\d{4}-\d{2}-\d{2}$/.test(v)&&!Number.isNaN(Date.parse(v))}
|
|
function date8(v){return v.replaceAll('-','')}
|
|
function show(){const start=$('start').value,end=$('end').value,pkg=$('pkg').value;if(!validDate(start)||!validDate(end)||start>end){status.textContent='请输入有效日期范围,且开始日期不得晚于结束日期。';status.className='status error';return}const filtered=rows.filter(r=>r.dt>=date8(start)&&r.dt<=date8(end)&&(!pkg||r.pkg_name===pkg));filtered.sort((a,b)=>{const av=a[sort.key],bv=b[sort.key];if(av===bv)return 0;if(av===null||av===undefined)return 1;if(bv===null||bv===undefined)return -1;const n=typeof av==='number'&&typeof bv==='number'?av-bv:String(av).localeCompare(String(bv));return n*sort.direction});$('body').replaceChildren();filtered.forEach(r=>{const tr=document.createElement('tr');columns.forEach(c=>{const td=document.createElement('td');td.textContent=text(r[c]);tr.append(td)});$('body').append(tr)});status.textContent=(failed.length?`数据不完整:跳过 ${failed.length} 个文件。`:'')+`显示 ${filtered.length} 行。`;status.className='status'+(failed.length?' error':'')}
|
|
function setupHeaders(){const h=$('head');columns.forEach(c=>{const th=document.createElement('th');th.dataset.key=c;th.textContent=c;th.addEventListener('click',()=>{if(sort.key===c)sort.direction*=-1;else{sort.key=c;sort.direction=-1}setupHeaders();show()});h.append(th)});}
|
|
async function load(){setupHeaders();try{const response=await fetch('data/index.json',{cache:'no-store'});if(!response.ok)throw Error('index.json 加载失败');const index=await response.json();if(!Array.isArray(index.files))throw Error('数据清单格式错误');const safe=index.files.filter(f=>/^\d{8}$/.test(f.date)&&f.path===`data/${f.date}.json`);const bad=index.files.length-safe.length;failed=bad?['清单路径']:[];const loaded=await Promise.all(safe.map(async f=>{try{const r=await fetch(f.path,{cache:'no-store'});if(!r.ok)throw Error('HTTP');const data=await r.json();if(!Array.isArray(data)||data.some(x=>!x||typeof x!=='object'||x.dt!==f.date))throw Error('格式');return data}catch(e){failed.push(f.date);return []}}));rows=loaded.flat();const dates=rows.map(r=>r.dt).sort();if(!dates.length){$('start').value=$('end').value='';status.textContent='暂无数据。';return}const end=dates.at(-1), endDate=new Date(`${end.slice(0,4)}-${end.slice(4,6)}-${end.slice(6)}T00:00:00`);endDate.setDate(endDate.getDate()-29);$('end').value=`${end.slice(0,4)}-${end.slice(4,6)}-${end.slice(6)}`;$('start').value=endDate.toISOString().slice(0,10);[...new Set(rows.map(r=>r.pkg_name).filter(Boolean))].sort().forEach(p=>{const o=document.createElement('option');o.value=p;o.textContent=p;$('pkg').append(o)});show()}catch(e){status.textContent=`无法加载数据:${e.message}`;status.className='status error'}}
|
|
$('apply').addEventListener('click',show);$('reset').addEventListener('click',()=>{if(rows.length){$('pkg').value='';const d=rows.map(r=>r.dt).sort().at(-1);$('end').value=`${d.slice(0,4)}-${d.slice(4,6)}-${d.slice(6)}`;const x=new Date($('end').value);x.setDate(x.getDate()-29);$('start').value=x.toISOString().slice(0,10);show()}});load();
|
|
})();
|
|
</script></body></html>
|