Files
claw/resources/zhihu-hotlist-echarts.html
2026-04-10 17:21:13 +08:00

638 lines
19 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" />
<title>知乎热榜图表驾驶舱</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
:root {
--bg: #06111f;
--bg-2: #0a1f37;
--panel: rgba(8, 25, 42, 0.88);
--panel-strong: rgba(10, 32, 55, 0.95);
--line: rgba(101, 187, 255, 0.18);
--line-strong: rgba(236, 186, 81, 0.26);
--text: #eef6ff;
--muted: #8ea6c2;
--accent: #62d0ff;
--accent-2: #ecba51;
--accent-3: #6df0c2;
--danger: #ff8b7e;
--shadow: 0 20px 48px rgba(0, 0, 0, 0.34);
--font-heading: "DIN Alternate", "Bahnschrift", "Microsoft YaHei UI", sans-serif;
--font-body: "Segoe UI Variable Text", "Microsoft YaHei", "PingFang SC", sans-serif;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
min-height: 100%;
background:
radial-gradient(circle at 16% 10%, rgba(98, 208, 255, 0.18), transparent 22%),
radial-gradient(circle at 86% 12%, rgba(236, 186, 81, 0.14), transparent 18%),
linear-gradient(145deg, var(--bg) 0%, var(--bg-2) 42%, #030910 100%);
color: var(--text);
font-family: var(--font-body);
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(101, 187, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(101, 187, 255, 0.05) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at center, black 34%, rgba(0, 0, 0, 0.22) 88%, transparent 100%);
}
.page {
min-height: 100vh;
padding: 18px;
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 14px;
}
.panel {
position: relative;
overflow: hidden;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.01)),
linear-gradient(145deg, rgba(9, 30, 51, 0.97), rgba(6, 20, 34, 0.92));
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: var(--shadow);
}
.panel::before {
content: "";
position: absolute;
left: 18px;
right: 18px;
top: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), var(--accent-2), transparent);
opacity: 0.95;
}
.hero {
padding: 18px 24px;
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 16px;
align-items: center;
}
.eyebrow {
color: var(--accent);
letter-spacing: 2px;
text-transform: uppercase;
font-size: 12px;
margin-bottom: 8px;
}
h1 {
margin: 0;
font-family: var(--font-heading);
font-size: 38px;
line-height: 1.08;
letter-spacing: 1px;
}
#snapshot-meta {
margin: 10px 0 0;
color: var(--muted);
font-size: 14px;
}
.hero-notes {
display: grid;
gap: 10px;
}
.note-card {
padding: 14px 16px;
border-radius: 16px;
background: linear-gradient(135deg, rgba(98, 208, 255, 0.08), rgba(236, 186, 81, 0.08));
border: 1px solid rgba(255, 255, 255, 0.05);
}
.note-card strong {
display: block;
margin-bottom: 6px;
font-size: 14px;
}
.note-card span {
color: var(--muted);
font-size: 12px;
line-height: 1.5;
}
.metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.metric {
padding: 18px 18px 16px;
}
.metric-label {
color: var(--muted);
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
.metric-value {
margin-top: 10px;
font-family: var(--font-heading);
font-size: 34px;
color: var(--text);
}
.metric-sub {
margin-top: 8px;
color: var(--accent);
font-size: 12px;
}
.charts {
min-height: 0;
display: grid;
grid-template-columns: 1.2fr 1fr 0.95fr;
grid-template-rows: 360px 320px;
gap: 14px;
grid-template-areas:
"bar top pie"
"bubble table table";
}
.chart-panel {
padding: 14px 16px 12px;
}
.bar-panel { grid-area: bar; }
.top-panel { grid-area: top; }
.pie-panel { grid-area: pie; }
.bubble-panel { grid-area: bubble; }
.table-panel { grid-area: table; padding: 14px 16px 10px; }
.section-head {
display: flex;
align-items: end;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.section-head h2 {
margin: 0;
font-size: 22px;
font-family: var(--font-heading);
letter-spacing: 1px;
}
.section-head span {
color: var(--muted);
font-size: 12px;
}
.chart {
width: 100%;
height: calc(100% - 42px);
}
.table-wrap {
height: calc(100% - 42px);
overflow: auto;
padding-right: 4px;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
position: sticky;
top: 0;
z-index: 1;
background: rgba(6, 19, 32, 0.96);
padding: 10px 8px;
text-align: left;
font-size: 12px;
color: var(--muted);
letter-spacing: 1px;
text-transform: uppercase;
border-bottom: 1px solid var(--line-strong);
}
tbody td {
padding: 11px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
font-size: 13px;
vertical-align: top;
}
tbody tr:nth-child(odd) {
background: rgba(255, 255, 255, 0.016);
}
.rank {
font-family: var(--font-heading);
color: var(--accent-2);
white-space: nowrap;
}
.heat {
color: var(--accent-3);
font-family: var(--font-heading);
white-space: nowrap;
}
.tag {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
background: rgba(98, 208, 255, 0.12);
color: var(--accent);
font-size: 12px;
}
.footer {
padding: 10px 16px;
color: var(--muted);
font-size: 12px;
}
@media (max-width: 1440px) {
.hero {
grid-template-columns: 1fr;
}
.metrics {
grid-template-columns: repeat(2, 1fr);
}
.charts {
grid-template-columns: 1fr;
grid-template-rows: 320px 320px 320px 320px 420px;
grid-template-areas:
"bar"
"top"
"pie"
"bubble"
"table";
}
}
@media (max-width: 760px) {
.page {
padding: 12px;
}
h1 {
font-size: 28px;
}
.metrics {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="page">
<section class="panel hero">
<div>
<div class="eyebrow">Zhihu Hotlist Visual Command Center</div>
<h1>知乎热榜图表驾驶舱</h1>
<p id="snapshot-meta">由 sgClaw screen_html_export 生成的本地静态展示页</p>
</div>
<div class="hero-notes">
<div class="note-card">
<strong>图表表达</strong>
<span>同一份热榜数据同时映射为分类热度、头部热点、结构占比和热度散点,适合现场讲解图表能力。</span>
</div>
<div class="note-card">
<strong>演示建议</strong>
<span id="lead-summary">优先讲解榜首热点、分类分布与热度层级,再向下展开全量榜单细节。</span>
</div>
</div>
</section>
<section class="metrics">
<article class="panel metric">
<div class="metric-label">热榜条目数</div>
<div id="metric-total" class="metric-value">0</div>
<div class="metric-sub">Tracked items</div>
</article>
<article class="panel metric">
<div class="metric-label">主题分类数</div>
<div id="metric-categories" class="metric-value">0</div>
<div class="metric-sub">Topic groups</div>
</article>
<article class="panel metric">
<div class="metric-label">累计热度</div>
<div id="metric-heat" class="metric-value">0</div>
<div class="metric-sub">Total heat</div>
</article>
<article class="panel metric">
<div class="metric-label">头部峰值</div>
<div id="metric-peak" class="metric-value">0</div>
<div class="metric-sub">Peak topic heat</div>
</article>
</section>
<section class="charts">
<section class="panel chart-panel bar-panel">
<div class="section-head">
<h2>分类总热度</h2>
<span>横向对比</span>
</div>
<div id="bar-chart" class="chart"></div>
</section>
<section class="panel chart-panel top-panel">
<div class="section-head">
<h2>Top10 热点</h2>
<span>柱状排行</span>
</div>
<div id="top-chart" class="chart"></div>
</section>
<section class="panel chart-panel pie-panel">
<div class="section-head">
<h2>分类占比</h2>
<span>环形结构</span>
</div>
<div id="pie-chart" class="chart"></div>
</section>
<section class="panel chart-panel bubble-panel">
<div class="section-head">
<h2>热度分层</h2>
<span>散点气泡</span>
</div>
<div id="bubble-chart" class="chart"></div>
</section>
<section class="panel table-panel">
<div class="section-head">
<h2>热榜明细</h2>
<span id="table-note">按原始顺序保留</span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>排名</th>
<th>标题</th>
<th>分类</th>
<th>热度</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
</section>
</section>
<section class="panel footer">
本页由 `screen_html_export` 生成,适合在系统浏览器中直接打开进行展示。
</section>
</div>
<script>
const defaultPayload = {
"snapshot_id": "template-snapshot",
"generated_at_ms": 0,
"categories": [],
"table": []
}
const themeMeta = {
title: "知乎热榜图表驾驶舱",
renderer: "screen_html_export"
};
const chartColors = ["#62d0ff", "#ecba51", "#6df0c2", "#7f8cff", "#ff8b7e", "#9fcbff", "#58a6ff"];
const charts = {};
function formatNumber(value) {
return new Intl.NumberFormat("zh-CN").format(Number(value || 0));
}
function getTotalHeat(categories) {
return (categories || []).reduce((sum, item) => sum + Number(item.total_heat || 0), 0);
}
function getPeakHeat(table) {
return (table || []).reduce((max, row) => Math.max(max, Number(row.heat_value || 0)), 0);
}
function buildLeadSummary(table, categories) {
const top = (table || [])[0];
const category = (categories || []).slice().sort((a, b) => (b.total_heat || 0) - (a.total_heat || 0))[0];
const parts = [];
if (top) {
parts.push(`榜首是“${top.title}`);
}
if (category) {
parts.push(`主导分类为“${category.category_label}`);
}
parts.push(`共覆盖 ${(table || []).length} 条热点`);
return parts.join("");
}
function ensureCharts() {
if (!window.echarts) {
return;
}
charts.bar = charts.bar || echarts.init(document.getElementById("bar-chart"));
charts.top = charts.top || echarts.init(document.getElementById("top-chart"));
charts.pie = charts.pie || echarts.init(document.getElementById("pie-chart"));
charts.bubble = charts.bubble || echarts.init(document.getElementById("bubble-chart"));
}
function renderBarChart(categories) {
const sorted = (categories || []).slice().sort((a, b) => Number(a.total_heat || 0) - Number(b.total_heat || 0));
charts.bar.setOption({
animationDuration: 700,
grid: {left: 90, right: 18, top: 10, bottom: 20},
xAxis: {
type: "value",
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
yAxis: {
type: "category",
data: sorted.map((item) => item.category_label),
axisLabel: {color: "#eef6ff"},
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
},
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
series: [{
type: "bar",
data: sorted.map((item, index) => ({
value: Number(item.total_heat || 0),
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [0, 8, 8, 0]}
})),
label: {show: true, position: "right", color: "#dfeeff"}
}]
});
}
function renderTopChart(table) {
const top = (table || []).slice(0, 10);
charts.top.setOption({
animationDuration: 700,
grid: {left: 42, right: 12, top: 26, bottom: 46},
tooltip: {trigger: "axis", axisPointer: {type: "shadow"}},
xAxis: {
type: "category",
data: top.map((row) => `#${row.rank}`),
axisLabel: {color: "#8ea6c2"},
axisLine: {lineStyle: {color: "rgba(255,255,255,0.1)"}}
},
yAxis: {
type: "value",
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
series: [{
type: "bar",
data: top.map((row, index) => ({
value: Number(row.heat_value || 0),
itemStyle: {color: chartColors[index % chartColors.length], borderRadius: [8, 8, 0, 0]}
})),
label: {show: true, position: "top", color: "#eef6ff", formatter: ({dataIndex}) => top[dataIndex].heat_text}
}]
});
}
function renderPieChart(categories) {
charts.pie.setOption({
animationDuration: 700,
color: chartColors,
tooltip: {trigger: "item"},
legend: {
bottom: 2,
textStyle: {color: "#8ea6c2", fontSize: 11},
itemWidth: 12,
itemHeight: 8
},
series: [{
type: "pie",
radius: ["44%", "72%"],
center: ["50%", "44%"],
itemStyle: {borderColor: "#081a2c", borderWidth: 2},
label: {
color: "#eef6ff",
formatter: "{b}\n{d}%"
},
data: (categories || []).map((item) => ({
name: item.category_label,
value: Number(item.total_heat || 0)
}))
}]
});
}
function renderBubbleChart(table) {
const top = (table || []).slice(0, 12);
charts.bubble.setOption({
animationDuration: 700,
color: chartColors,
grid: {left: 44, right: 18, top: 16, bottom: 36},
xAxis: {
type: "value",
name: "排名",
inverse: true,
min: 0,
max: Math.max(...top.map((row) => Number(row.rank || 0)), 10) + 1,
nameTextStyle: {color: "#8ea6c2"},
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
yAxis: {
type: "value",
name: "热度值",
nameTextStyle: {color: "#8ea6c2"},
axisLabel: {color: "#8ea6c2"},
splitLine: {lineStyle: {color: "rgba(255,255,255,0.06)"}}
},
tooltip: {
formatter: (params) => {
const row = params.data.raw;
return `${row.title}<br/>排名 #${row.rank}<br/>热度 ${row.heat_text}<br/>分类 ${row.category_label}`;
}
},
series: [{
type: "scatter",
symbolSize: (value) => Math.max(18, Math.min(56, value[2] / 80000)),
data: top.map((row, index) => ({
value: [Number(row.rank || 0), Number(row.heat_value || 0), Number(row.heat_value || 0)],
raw: row,
itemStyle: {color: chartColors[index % chartColors.length], opacity: 0.82}
}))
}]
});
}
function renderTable(table) {
document.getElementById("table-body").innerHTML = (table || []).map((row) => `
<tr>
<td class="rank">#${row.rank}</td>
<td>${row.title}</td>
<td><span class="tag">${row.category_label}</span></td>
<td class="heat">${row.heat_text}</td>
</tr>
`).join("");
}
function render(payload) {
const data = payload || defaultPayload;
const categories = data.categories || [];
const table = data.table || [];
document.title = themeMeta.title;
document.getElementById("snapshot-meta").textContent =
`${data.snapshot_id} | 生成时间 ${new Date(data.generated_at_ms || 0).toLocaleString()}`;
document.getElementById("metric-total").textContent = formatNumber(table.length);
document.getElementById("metric-categories").textContent = formatNumber(categories.length);
document.getElementById("metric-heat").textContent = formatNumber(getTotalHeat(categories));
document.getElementById("metric-peak").textContent = formatNumber(getPeakHeat(table));
document.getElementById("lead-summary").textContent = buildLeadSummary(table, categories);
document.getElementById("table-note").textContent =
table.length > 0 ? `当前展示 ${table.length} 条热点` : "暂无热榜数据";
renderTable(table);
ensureCharts();
if (window.echarts) {
renderBarChart(categories);
renderTopChart(table);
renderPieChart(categories);
renderBubbleChart(table);
}
}
window.addEventListener("resize", () => {
Object.values(charts).forEach((chart) => chart && chart.resize());
});
render(defaultPayload);
</script>
</body>
</html>