chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
@@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{$title | default='数据概览'}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<link href="/assets/ywxapp/css/ywxapp.css" rel="stylesheet">
<style>
body { padding: 16px; background: #f5f6fa; }
.stat-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px; }
.stat-card { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 1px 6px rgba(0,0,0,.06); }
.stat-card .num { font-size: 26px; font-weight: 700; color: #1E9FFF; }
.stat-card .lbl { color: #666; font-size: 13px; margin-top: 4px; }
.panel { background: #fff; border-radius: 8px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 6px rgba(0,0,0,.06); }
.panel h3 { font-size: 15px; margin-bottom: 12px; border-left: 3px solid #1E9FFF; padding-left: 8px; }
.bar-row { display: flex; align-items: center; margin-bottom: 8px; }
.bar-row .name { width: 120px; color: #333; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-row .bar { flex: 1; background: #eef1f6; border-radius: 4px; height: 16px; margin: 0 8px; overflow: hidden; }
.bar-row .bar > i { display: block; height: 100%; background: linear-gradient(90deg,#1E9FFF,#5FB878); }
.bar-row .val { width: 60px; text-align: right; color: #999; font-size: 12px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 900px) { .stat-cards { grid-template-columns: repeat(2,1fr); } .grid2 { grid-template-columns: 1fr; } }
.heat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px,1fr)); gap: 8px; }
.heat-cell { border-radius: 6px; padding: 10px 12px; color: #333; min-height: 54px; box-shadow: 0 1px 3px rgba(0,0,0,.06); line-height: 1.5; }
.heat-cell b { font-size: 18px; }
.heat-table { border-collapse: collapse; width: 100%; }
.heat-table th { font-size: 11px; color: #999; font-weight: normal; padding: 2px 1px; }
.heat-table td { height: 16px; border-radius: 2px; }
.hh { width: 14px; }
.dh { text-align: right; padding-right: 6px; white-space: nowrap; }
</style>
</head>
<body>
<div class="stat-cards" id="statCards"></div>
<div class="grid2">
<div class="panel">
<h3>Top 分类(按链接数)</h3>
<div id="topCategories"></div>
</div>
<div class="panel">
<h3>热门点击 Top10</h3>
<table class="layui-table" lay-size="sm">
<thead><tr><th>网站</th><th style="width:90px">点击</th></tr></thead>
<tbody id="topClicks"></tbody>
</table>
</div>
</div>
<div class="panel">
<h3>点击热力图 · 星期×小时 <span id="timeMeta" style="font-weight:normal;color:#999;font-size:12px;"></span></h3>
<div id="timeHeat"></div>
</div>
<div class="panel">
<h3>分类点击热度</h3>
<div id="categoryHeat" class="heat-grid"></div>
</div>
<div class="panel">
<h3>死链概览 <span id="deadMeta" style="font-weight:normal;color:#999;font-size:12px;"></span></h3>
<table class="layui-table" lay-size="sm">
<thead><tr><th>网站</th><th style="width:280px">网址</th><th style="width:90px">状态码</th><th style="width:140px">最近检测</th></tr></thead>
<tbody id="deadList"></tbody>
</table>
</div>
<script src="/assets/layui/layui.js"></script>
<script src="/assets/ywxapp/ywxapp.js" module="backend"></script>
<script>
function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];}); }
// 点击密度 -> 颜色(浅米黄=低,深红=高)
function heatColor(ratio){
ratio = Math.max(0, Math.min(1, ratio));
var r = 255, g = Math.round(245 - 200*ratio), b = Math.round(230 - 210*ratio);
return 'rgb('+r+','+g+','+b+')';
}
layui.use(['http'], function () {
var http = layui.http;
http.get('index').then(function (res) {
var d = (res && res.data) || {};
renderCards(d.summary || {});
renderCategories(d.topCategories || []);
renderClicks(d.topClicks || []);
renderDead(d.dead || {});
if (d.heatmap) {
renderTimeHeat(d.heatmap.time || [], d.heatmap.timeRange || '', d.heatmap.timeTotal || 0);
renderCategoryHeat(d.heatmap.category || []);
}
});
});
function renderCards(s){
var cards = [
['链接总数', s.links], ['已上架', s.online], ['待审核', s.pending], ['已禁用', s.disabled],
['分类数', s.categories], ['热门', s.hot], ['推荐', s.recommend], ['总点击', s.clicks]
];
document.getElementById('statCards').innerHTML = cards.map(function(c){
return '<div class="stat-card"><div class="num">'+(c[1]||0)+'</div><div class="lbl">'+c[0]+'</div></div>';
}).join('');
}
function renderCategories(list){
var max = 1; list.forEach(function(c){ if(c.count>max) max=c.count; });
document.getElementById('topCategories').innerHTML = list.map(function(c){
var pct = Math.round(c.count/max*100);
return '<div class="bar-row"><span class="name">'+(c.icon||'')+' '+esc(c.title)+'</span>'+
'<span class="bar"><i style="width:'+pct+'%"></i></span><span class="val">'+c.count+'</span></div>';
}).join('') || '<p style="color:#999">暂无数据</p>';
}
function renderClicks(list){
document.getElementById('topClicks').innerHTML = list.map(function(it){
return '<tr><td><a href="'+esc(it.url)+'" target="_blank">'+esc(it.title)+'</a></td><td>'+(it.click_count||0)+'</td></tr>';
}).join('') || '<tr><td colspan="2" style="color:#999">暂无数据</td></tr>';
}
function renderTimeHeat(heat, meta, total){
var days = ['周日','周一','周二','周三','周四','周五','周六'];
var max = 1;
for (var d = 0; d < 7; d++) { for (var h = 0; h < 24; h++) { var v = (heat[d] && heat[d][h]) || 0; if (v > max) max = v; } }
document.getElementById('timeMeta').textContent = '' + meta + ',共 ' + total + ' 次点击)';
if (!heat.length) { document.getElementById('timeHeat').innerHTML = '<p style="color:#999">暂无点击数据(需先有访客点击跳转)</p>'; return; }
var html = '<table class="heat-table"><thead><tr><th></th>';
for (var h = 0; h < 24; h++) { html += '<th class="hh">' + (h % 24) + '</th>'; }
html += '</tr></thead><tbody>';
for (var d = 0; d < 7; d++) {
html += '<tr><th class="dh">' + days[d] + '</th>';
for (var h = 0; h < 24; h++) {
var v = (heat[d] && heat[d][h]) || 0;
var ratio = v / max;
html += '<td style="background:' + heatColor(ratio) + '" title="' + days[d] + ' ' + h + ':00 点击 ' + v + ' 次"></td>';
}
html += '</tr>';
}
html += '</tbody></table>';
document.getElementById('timeHeat').innerHTML = html;
}
function renderCategoryHeat(list){
if (!list || !list.length) { document.getElementById('categoryHeat').innerHTML = '<p style="color:#999">暂无数据</p>'; return; }
var max = 1; list.forEach(function(c){ if (c.clicks > max) max = c.clicks; });
document.getElementById('categoryHeat').innerHTML = list.map(function(c){
var ratio = c.clicks / max;
var txt = ratio > 0.55 ? '#fff' : '#333';
return '<div class="heat-cell" style="background:' + heatColor(ratio) + ';color:' + txt + '" title="' + esc(c.title) + '' + (c.clicks || 0) + ' 次">' +
(c.icon ? c.icon + ' ' : '') + esc(c.title) + '<br><b>' + (c.clicks || 0) + '</b></div>';
}).join('');
}
function renderDead(d){
document.getElementById('deadMeta').textContent = d.last_check_at ? ('(共 '+ (d.count||0) +' 条,最近检测 '+d.last_check_at+'') : '(尚未执行死链检测)';
var list = d.list || [];
document.getElementById('deadList').innerHTML = list.map(function(it){
var t = it.last_check_at ? new Date(it.last_check_at*1000).toLocaleString() : '-';
return '<tr><td>'+esc(it.title)+'</td><td style="word-break:break-all">'+esc(it.url)+'</td><td>'+(it.status_code===0?'无法访问':it.status_code)+'</td><td>'+t+'</td></tr>';
}).join('') || '<tr><td colspan="4" style="color:#999">无死链或尚未检测</td></tr>';
}
</script>
</body>
</html>