Files
skill-lib/skills/zhihu-hotlist-screen/assets/zhihu-hotlist-echarts.html
木炎 e4283f04cc feat: refresh Zhihu dashboard and draft autofill
Refresh the Zhihu hotlist screen presentation and update the article draft autofill flow to match the latest interaction changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 21:54:29 +08:00

1432 lines
50 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN" data-theme="gov_blue_gold">
<head>
<meta charset="UTF-8">
<title>知乎热榜主题分类分析大屏</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--font-heading: "Microsoft YaHei UI", "PingFang SC", "Hiragino Sans GB", sans-serif;
--font-body: "Segoe UI Variable Text", "Microsoft YaHei", "PingFang SC", sans-serif;
--font-data: "Bahnschrift", "DIN Alternate", "Segoe UI Variable Display", sans-serif;
--bg: #06162c;
--bg-alt: #0a2442;
--bg-soft: #12345c;
--panel: rgba(7, 25, 48, 0.84);
--panel-strong: rgba(8, 29, 57, 0.95);
--panel-soft: rgba(10, 37, 70, 0.76);
--text: #f4f8ff;
--muted: #95acc6;
--accent: #d5b16a;
--accent-2: #35d4ff;
--accent-3: #5d8cff;
--success: #5fe5d7;
--line: rgba(94, 169, 255, 0.18);
--line-strong: rgba(213, 177, 106, 0.36);
--glow: rgba(53, 212, 255, 0.18);
--shadow: 0 28px 60px rgba(1, 8, 22, 0.42);
--header-kicker: #35d4ff;
--table-header: rgba(4, 17, 34, 0.92);
--summary-note-bg: linear-gradient(120deg, rgba(53, 212, 255, 0.07), rgba(255, 255, 255, 0.015));
--panel-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)), linear-gradient(145deg, rgba(8, 29, 57, 0.97), rgba(7, 22, 43, 0.90));
}
html[data-theme="gov_blue_gold"] {
--bg: #06162c;
--bg-alt: #0a2442;
--bg-soft: #12345c;
--panel: rgba(7, 25, 48, 0.84);
--panel-strong: rgba(8, 29, 57, 0.95);
--panel-soft: rgba(10, 37, 70, 0.76);
--text: #f4f8ff;
--muted: #95acc6;
--accent: #d5b16a;
--accent-2: #35d4ff;
--accent-3: #5d8cff;
--success: #5fe5d7;
--line: rgba(94, 169, 255, 0.18);
--line-strong: rgba(213, 177, 106, 0.36);
--glow: rgba(53, 212, 255, 0.18);
--header-kicker: #35d4ff;
--table-header: rgba(4, 17, 34, 0.92);
--summary-note-bg: linear-gradient(120deg, rgba(53, 212, 255, 0.07), rgba(255, 255, 255, 0.015));
--panel-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)), linear-gradient(145deg, rgba(8, 29, 57, 0.97), rgba(7, 22, 43, 0.90));
}
html[data-theme="tech_cyan_blue"] {
--bg: #051528;
--bg-alt: #082740;
--bg-soft: #103b63;
--panel: rgba(5, 21, 40, 0.84);
--panel-strong: rgba(6, 27, 49, 0.95);
--panel-soft: rgba(11, 40, 68, 0.78);
--text: #ebfbff;
--muted: #8fb9d2;
--accent: #73f0ff;
--accent-2: #22d3ee;
--accent-3: #5b8cff;
--success: #7df0cf;
--line: rgba(34, 211, 238, 0.20);
--line-strong: rgba(91, 140, 255, 0.34);
--glow: rgba(34, 211, 238, 0.22);
--header-kicker: #73f0ff;
--table-header: rgba(4, 20, 38, 0.94);
--summary-note-bg: linear-gradient(120deg, rgba(34, 211, 238, 0.09), rgba(255, 255, 255, 0.015));
--panel-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)), linear-gradient(145deg, rgba(6, 26, 45, 0.97), rgba(7, 20, 35, 0.90));
}
html[data-theme="industry_ink_green"] {
--bg: #091919;
--bg-alt: #103030;
--bg-soft: #184747;
--panel: rgba(8, 24, 24, 0.86);
--panel-strong: rgba(12, 37, 37, 0.95);
--panel-soft: rgba(16, 54, 54, 0.78);
--text: #edf8f4;
--muted: #9abbb0;
--accent: #d1b26f;
--accent-2: #74c69d;
--accent-3: #3dd6b5;
--success: #9af3d0;
--line: rgba(116, 198, 157, 0.18);
--line-strong: rgba(209, 178, 111, 0.32);
--glow: rgba(116, 198, 157, 0.18);
--header-kicker: #74c69d;
--table-header: rgba(7, 22, 22, 0.94);
--summary-note-bg: linear-gradient(120deg, rgba(116, 198, 157, 0.09), rgba(255, 255, 255, 0.015));
--panel-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)), linear-gradient(145deg, rgba(10, 29, 29, 0.97), rgba(8, 22, 22, 0.90));
}
html[data-theme="meeting_red_gold"] {
--bg: #1a0b0b;
--bg-alt: #311313;
--bg-soft: #4a1919;
--panel: rgba(28, 10, 10, 0.86);
--panel-strong: rgba(45, 17, 17, 0.95);
--panel-soft: rgba(60, 20, 20, 0.78);
--text: #fff3f1;
--muted: #c9a7a1;
--accent: #d79a43;
--accent-2: #ef6a57;
--accent-3: #ff8d7d;
--success: #ffc78d;
--line: rgba(239, 106, 87, 0.18);
--line-strong: rgba(215, 154, 67, 0.34);
--glow: rgba(239, 106, 87, 0.18);
--header-kicker: #ff8d7d;
--table-header: rgba(24, 8, 8, 0.94);
--summary-note-bg: linear-gradient(120deg, rgba(239, 106, 87, 0.09), rgba(255, 255, 255, 0.015));
--panel-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01)), linear-gradient(145deg, rgba(34, 11, 11, 0.97), rgba(24, 8, 8, 0.90));
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background:
radial-gradient(circle at 20% 10%, var(--glow), transparent 26%),
radial-gradient(circle at 84% 18%, rgba(213, 177, 106, 0.10), transparent 24%),
linear-gradient(145deg, #041121 0%, var(--bg-alt) 52%, var(--bg) 100%);
}
body {
font-family: var(--font-body);
color: var(--text);
}
.viewport-shell {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
background:
radial-gradient(circle at 50% 50%, rgba(93, 140, 255, 0.08), transparent 42%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0));
}
.dashboard-canvas {
position: absolute;
left: 50%;
top: 50%;
width: 1920px;
height: 1080px;
transform-origin: center center;
}
.dashboard-canvas::before,
.dashboard-canvas::after {
content: "";
position: absolute;
inset: 24px;
border: 1px solid rgba(53, 212, 255, 0.08);
pointer-events: none;
}
.dashboard-canvas::after {
inset: 44px;
border-color: rgba(213, 177, 106, 0.08);
}
.bg-grid,
.bg-grid::before {
position: absolute;
inset: 0;
pointer-events: none;
content: "";
}
.bg-grid {
background-image:
linear-gradient(rgba(101, 162, 255, 0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(101, 162, 255, 0.06) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: radial-gradient(circle at center, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 0.25) 85%, transparent 100%);
}
.bg-grid::before {
background:
radial-gradient(circle at 16% 24%, var(--glow), transparent 0 16%),
radial-gradient(circle at 78% 22%, rgba(93, 140, 255, 0.16), transparent 0 18%),
radial-gradient(circle at 76% 82%, rgba(213, 177, 106, 0.13), transparent 0 17%);
animation: fieldPulse 10s ease-in-out infinite alternate;
}
.screen {
position: relative;
display: grid;
width: 100%;
height: 100%;
padding: 28px;
gap: 20px;
grid-template-columns: 420px minmax(0, 1fr) 456px;
grid-template-rows: 108px 340px minmax(0, 1fr) 54px;
grid-template-areas:
"header header header"
"summary bar pie"
"table table table"
"footer footer footer";
}
.panel {
position: relative;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 24px;
background: var(--panel-bg);
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
transition: background 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
}
.panel::before {
content: "";
position: absolute;
left: 18px;
right: 18px;
top: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent-2), var(--accent), transparent);
opacity: 0.92;
}
.panel::after {
content: "";
position: absolute;
inset: 14px;
border: 1px solid rgba(255, 255, 255, 0.04);
border-radius: 18px;
pointer-events: none;
}
.section-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
position: relative;
z-index: 1;
}
.section-kicker {
font-size: 12px;
letter-spacing: 2px;
color: var(--accent-2);
text-transform: uppercase;
opacity: 0.92;
}
.section-title {
font-family: var(--font-heading);
font-size: 24px;
font-weight: 700;
letter-spacing: 1px;
color: var(--text);
margin-top: 6px;
}
.section-subtitle {
font-size: 13px;
color: var(--muted);
max-width: 420px;
line-height: 1.5;
}
.header {
grid-area: header;
display: grid;
grid-template-columns: minmax(0, 1fr) 780px 196px;
align-items: stretch;
gap: 16px;
padding: 18px 26px;
}
.header::after {
inset: 12px;
}
.header-title-group {
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
min-width: 0;
position: relative;
z-index: 1;
}
.header-kicker {
font-size: 13px;
letter-spacing: 4px;
color: var(--header-kicker);
text-transform: uppercase;
}
.header-title {
font-family: var(--font-heading);
font-size: 34px;
font-weight: 700;
letter-spacing: 2px;
color: var(--text);
text-shadow: 0 0 22px var(--glow);
}
.header-subtitle {
font-size: 14px;
color: var(--muted);
letter-spacing: 0.2px;
}
.header-meta {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
position: relative;
z-index: 1;
}
.meta-card {
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
padding: 12px 16px;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.05);
background: linear-gradient(160deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
}
.meta-label {
font-size: 11px;
color: var(--muted);
letter-spacing: 1.5px;
text-transform: uppercase;
}
.meta-value {
margin-top: 7px;
font-size: 18px;
font-family: var(--font-data);
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.theme-switcher {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
align-content: center;
}
.theme-btn {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
background: linear-gradient(140deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
color: var(--muted);
cursor: pointer;
padding: 10px 10px 8px;
transition: transform 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
min-width: 0;
}
.theme-btn:hover {
transform: translateY(-1px);
border-color: rgba(255, 255, 255, 0.18);
color: var(--text);
}
.theme-btn.active {
color: var(--text);
border-color: var(--accent-2);
box-shadow: 0 0 0 1px rgba(53, 212, 255, 0.16), 0 10px 24px rgba(3, 12, 24, 0.20);
}
.theme-chip {
width: 100%;
height: 10px;
border-radius: 999px;
margin-bottom: 8px;
}
.theme-btn[data-theme="gov_blue_gold"] .theme-chip { background: linear-gradient(90deg, #35d4ff, #d5b16a); }
.theme-btn[data-theme="tech_cyan_blue"] .theme-chip { background: linear-gradient(90deg, #22d3ee, #5b8cff); }
.theme-btn[data-theme="industry_ink_green"] .theme-chip { background: linear-gradient(90deg, #74c69d, #d1b26f); }
.theme-btn[data-theme="meeting_red_gold"] .theme-chip { background: linear-gradient(90deg, #ef6a57, #d79a43); }
.theme-label {
display: block;
font-size: 11px;
line-height: 1.2;
letter-spacing: 0.4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.summary-panel {
grid-area: summary;
padding: 24px;
display: flex;
flex-direction: column;
min-height: 0;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
position: relative;
z-index: 1;
}
.kpi-card {
position: relative;
min-height: 100px;
padding: 16px 16px 14px;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.05);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.02));
overflow: hidden;
}
.kpi-card::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(180deg, var(--accent-2), var(--accent));
opacity: 0.96;
}
.kpi-card:nth-child(2)::before { background: linear-gradient(180deg, var(--accent), #f3d89e); }
.kpi-card:nth-child(3)::before { background: linear-gradient(180deg, var(--accent-3), var(--accent-2)); }
.kpi-card:nth-child(4)::before { background: linear-gradient(180deg, var(--success), var(--accent)); }
.kpi-label {
font-size: 11px;
color: var(--muted);
letter-spacing: 1.2px;
text-transform: uppercase;
}
.kpi-value {
margin-top: 10px;
font-size: 30px;
line-height: 1;
font-family: var(--font-data);
font-weight: 700;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.kpi-value.long {
font-size: 18px;
line-height: 1.2;
font-family: var(--font-heading);
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.kpi-sub {
margin-top: 8px;
font-size: 12px;
color: var(--muted);
line-height: 1.45;
}
.summary-notes {
margin-top: 16px;
display: grid;
gap: 10px;
position: relative;
z-index: 1;
}
.summary-note {
display: grid;
grid-template-columns: 86px 1fr;
gap: 10px;
align-items: start;
padding: 12px 14px;
border-radius: 16px;
background: var(--summary-note-bg);
border: 1px solid rgba(255, 255, 255, 0.06);
}
.summary-note-tag {
font-size: 11px;
letter-spacing: 1px;
color: var(--accent);
text-transform: uppercase;
}
.summary-note-text {
font-size: 13px;
line-height: 1.5;
color: var(--text);
word-break: break-word;
}
.bar-panel {
grid-area: bar;
padding: 20px 22px 16px;
display: flex;
flex-direction: column;
min-height: 0;
}
.bar-chart-wrap {
position: relative;
flex: 1;
min-height: 0;
z-index: 1;
}
#barChart {
width: 100%;
height: 100%;
min-height: 0;
}
.pie-panel {
grid-area: pie;
padding: 20px 20px 16px;
display: grid;
grid-template-rows: auto 190px minmax(0, 1fr);
min-height: 0;
}
.pie-chart-wrap {
position: relative;
z-index: 1;
}
#pieChart {
width: 100%;
height: 190px;
}
.category-board {
position: relative;
z-index: 1;
display: grid;
align-content: start;
gap: 10px;
padding-top: 4px;
}
.category-row {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
padding: 10px 12px;
border-radius: 15px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.015));
border: 1px solid rgba(255, 255, 255, 0.04);
}
.category-dot {
width: 10px;
height: 10px;
border-radius: 50%;
box-shadow: 0 0 10px currentColor;
}
.category-name {
min-width: 0;
font-size: 14px;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-metrics {
text-align: right;
font-size: 12px;
line-height: 1.4;
color: var(--muted);
white-space: nowrap;
}
.table-panel {
grid-area: table;
padding: 20px 22px 18px;
display: flex;
flex-direction: column;
min-height: 0;
}
.table-head-meta {
font-size: 13px;
color: var(--muted);
white-space: nowrap;
}
.table-grid {
--table-columns: 1;
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: repeat(var(--table-columns), minmax(0, 1fr));
gap: 14px;
position: relative;
z-index: 1;
}
.table-pane {
min-width: 0;
overflow: hidden;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.05);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.015));
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
thead {
background: var(--table-header);
}
th {
padding: 12px 12px;
border-bottom: 1px solid var(--line);
font-size: 12px;
color: var(--muted);
letter-spacing: 1px;
text-align: left;
font-weight: 600;
}
td {
padding: 10px 12px;
border-bottom: 1px solid rgba(94, 169, 255, 0.09);
font-size: 14px;
color: var(--text);
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
tbody tr:last-child td {
border-bottom: 0;
}
th:first-child,
td:first-child {
width: 64px;
text-align: center;
}
th:nth-child(3),
td:nth-child(3) {
width: 110px;
}
th:nth-child(4),
td:nth-child(4) {
width: 128px;
text-align: right;
font-family: var(--font-data);
color: var(--accent);
font-weight: 700;
}
.rank-chip {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: linear-gradient(145deg, rgba(93, 140, 255, 0.22), rgba(53, 212, 255, 0.10));
border: 1px solid rgba(93, 140, 255, 0.18);
color: var(--text);
font-family: var(--font-data);
font-size: 15px;
font-weight: 700;
}
.rank-chip.top-1 {
background: linear-gradient(145deg, rgba(213, 177, 106, 0.86), rgba(255, 218, 138, 0.72));
color: #241400;
border-color: rgba(255, 232, 176, 0.92);
}
.rank-chip.top-2 {
background: linear-gradient(145deg, rgba(169, 189, 224, 0.78), rgba(224, 233, 247, 0.66));
color: #10223e;
border-color: rgba(229, 237, 247, 0.82);
}
.rank-chip.top-3 {
background: linear-gradient(145deg, rgba(195, 132, 63, 0.86), rgba(232, 165, 95, 0.70));
color: #fff7ef;
border-color: rgba(242, 194, 140, 0.72);
}
.title-cell {
font-size: 14px;
line-height: 1.45;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-tag {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 64px;
padding: 5px 10px;
border-radius: 999px;
border: 1px solid currentColor;
font-size: 12px;
line-height: 1;
background: rgba(255, 255, 255, 0.04);
}
.table-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
border-radius: 18px;
border: 1px dashed rgba(255, 255, 255, 0.14);
font-size: 15px;
color: var(--muted);
}
.footer {
grid-area: footer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 22px;
font-size: 13px;
color: var(--muted);
}
.footer strong {
color: var(--text);
font-weight: 600;
}
.footer-right {
display: flex;
align-items: center;
gap: 20px;
white-space: nowrap;
}
@keyframes fieldPulse {
from { opacity: 0.55; transform: scale(1); }
to { opacity: 0.95; transform: scale(1.03); }
}
</style>
</head>
<body>
<div class="viewport-shell">
<div class="dashboard-canvas" id="dashboardCanvas">
<div class="bg-grid"></div>
<div class="screen">
<header class="panel header">
<div class="header-title-group">
<div class="header-kicker">国企舆情监测专题汇报</div>
<div class="header-title">知乎热榜主题分类分析大屏</div>
<div class="header-subtitle">按国企汇报风格强化科技观感,保留主题切换能力,并确保全量热榜数据一屏展示完成。</div>
</div>
<div class="header-meta">
<div class="meta-card">
<div class="meta-label">快照编号</div>
<div class="meta-value" id="snapshotId">-</div>
</div>
<div class="meta-card">
<div class="meta-label">数据生成时间</div>
<div class="meta-value" id="generatedAt">-</div>
</div>
<div class="meta-card">
<div class="meta-label">当前展示时间</div>
<div class="meta-value" id="currentTime">-</div>
</div>
</div>
<div class="theme-switcher" id="themeSwitcher">
<button class="theme-btn active" type="button" data-theme="gov_blue_gold">
<span class="theme-chip"></span>
<span class="theme-label">政务蓝金</span>
</button>
<button class="theme-btn" type="button" data-theme="tech_cyan_blue">
<span class="theme-chip"></span>
<span class="theme-label">科技青蓝</span>
</button>
<button class="theme-btn" type="button" data-theme="industry_ink_green">
<span class="theme-chip"></span>
<span class="theme-label">工业墨绿</span>
</button>
<button class="theme-btn" type="button" data-theme="meeting_red_gold">
<span class="theme-chip"></span>
<span class="theme-label">会议红金</span>
</button>
</div>
</header>
<section class="panel summary-panel">
<div class="section-head">
<div>
<div class="section-kicker">Executive Brief</div>
<div class="section-title">汇报摘要</div>
</div>
<div class="section-subtitle">基于 ui-ux-pro-max 中关于数据大屏、图表和专业风格的规则,增强信息层次、色彩秩序和数据可读性。</div>
</div>
<div class="summary-grid">
<div class="kpi-card" id="kpi0">
<div class="kpi-label">热榜条目数</div>
<div class="kpi-value">-</div>
<div class="kpi-sub">-</div>
</div>
<div class="kpi-card" id="kpi1">
<div class="kpi-label">总热度规模</div>
<div class="kpi-value">-</div>
<div class="kpi-sub">-</div>
</div>
<div class="kpi-card" id="kpi2">
<div class="kpi-label">最高热话题</div>
<div class="kpi-value long">-</div>
<div class="kpi-sub">-</div>
</div>
<div class="kpi-card" id="kpi3">
<div class="kpi-label">主题分类数</div>
<div class="kpi-value">-</div>
<div class="kpi-sub">-</div>
</div>
</div>
<div class="summary-notes" id="summaryNotes"></div>
</section>
<section class="panel bar-panel">
<div class="section-head">
<div>
<div class="section-kicker">Heat Ranking</div>
<div class="section-title">重点热度排行</div>
</div>
<div class="section-subtitle">使用横向条形图承载前十热点,符合 ui-ux-pro-max 对比较型数据优先使用条形图的建议。</div>
</div>
<div class="bar-chart-wrap">
<div id="barChart"></div>
</div>
</section>
<section class="panel pie-panel">
<div class="section-head">
<div>
<div class="section-kicker">Category Structure</div>
<div class="section-title">分类分布态势</div>
</div>
<div class="section-subtitle">保留分类可视化并增加右侧结构清单,避免只依赖颜色表达信息。</div>
</div>
<div class="pie-chart-wrap">
<div id="pieChart"></div>
</div>
<div class="category-board" id="categoryBoard"></div>
</section>
<section class="panel table-panel">
<div class="section-head">
<div>
<div class="section-kicker">Full Coverage</div>
<div class="section-title">全量明细数据</div>
</div>
<div class="table-head-meta" id="tableMeta">一屏展示全部数据,不出现滚动条</div>
</div>
<div class="table-grid" id="tableWrap"></div>
</section>
<footer class="panel footer">
<div>汇报主题:<strong>知乎热榜主题分类分析</strong></div>
<div class="footer-right">
<span>生成时间:<strong id="footerGeneratedAt">-</strong></span>
<span>展示风格:<strong id="footerTheme">政务蓝金</strong></span>
<span>sgClaw 智能分析出品</span>
</div>
</footer>
</div>
</div>
</div>
<script>
const defaultPayload = {
snapshot_id: "demo-snapshot-20260328",
generated_at_ms: 1774628400000,
categories: [
{ category_code: "society", category_label: "社会", item_count: 3, total_heat: 138000, avg_heat: 46000 },
{ category_code: "entertainment", category_label: "娱乐", item_count: 2, total_heat: 116000, avg_heat: 58000 },
{ category_code: "technology", category_label: "科技", item_count: 2, total_heat: 103000, avg_heat: 51500 },
{ category_code: "sports", category_label: "体育", item_count: 2, total_heat: 93000, avg_heat: 46500 },
{ category_code: "military", category_label: "军事", item_count: 1, total_heat: 42000, avg_heat: 42000 },
{ category_code: "finance", category_label: "财经", item_count: 1, total_heat: 39000, avg_heat: 39000 }
],
table: [
{ rank: 1, title: "明星新电影票房破纪录,娱乐板块热度再度走高", url: "https://www.zhihu.com/question/100001", category_code: "entertainment", category_label: "娱乐", heat_text: "6.8 万热度", heat_value: 68000, reply_count: 132, upvote_count: 865, favorite_count: 215, heart_count: 96 },
{ rank: 2, title: "AI 智能体落地企业办公场景,科技话题持续升温", url: "https://www.zhihu.com/question/100002", category_code: "technology", category_label: "科技", heat_text: "6.2 万热度", heat_value: 62000, reply_count: 110, upvote_count: 790, favorite_count: 188, heart_count: 75 },
{ rank: 3, title: "城市更新与民生配套建设引发社会讨论", url: "https://www.zhihu.com/question/100003", category_code: "society", category_label: "社会", heat_text: "5.5 万热度", heat_value: 55000, reply_count: 96, upvote_count: 602, favorite_count: 144, heart_count: 52 },
{ rank: 4, title: "国足新周期备战方案公布,体育板块关注度回升", url: "https://www.zhihu.com/question/100004", category_code: "sports", category_label: "体育", heat_text: "5.2 万热度", heat_value: 52000, reply_count: 84, upvote_count: 573, favorite_count: 116, heart_count: 49 },
{ rank: 5, title: "航母编队演训画面公布,军事类目成为高热讨论点", url: "https://www.zhihu.com/question/100005", category_code: "military", category_label: "军事", heat_text: "4.2 万热度", heat_value: 42000, reply_count: 75, upvote_count: 418, favorite_count: 103, heart_count: 44 },
{ rank: 6, title: "居民消费新政发布后,财经与社会议题出现联动", url: "https://www.zhihu.com/question/100006", category_code: "finance", category_label: "财经", heat_text: "3.9 万热度", heat_value: 39000, reply_count: 68, upvote_count: 366, favorite_count: 91, heart_count: 28 },
{ rank: 7, title: "短剧与综艺互动模式创新,娱乐内容二次发酵", url: "https://www.zhihu.com/question/100007", category_code: "entertainment", category_label: "娱乐", heat_text: "4.8 万热度", heat_value: 48000, reply_count: 62, upvote_count: 312, favorite_count: 74, heart_count: 26 },
{ rank: 8, title: "具身智能新进展吸引开发者讨论,科技类目热度稳定", url: "https://www.zhihu.com/question/100008", category_code: "technology", category_label: "科技", heat_text: "4.1 万热度", heat_value: 41000, reply_count: 55, upvote_count: 295, favorite_count: 67, heart_count: 25 },
{ rank: 9, title: "大型赛事带动城市消费,体育与社会话题并行上涨", url: "https://www.zhihu.com/question/100009", category_code: "sports", category_label: "体育", heat_text: "4.1 万热度", heat_value: 41000, reply_count: 49, upvote_count: 248, favorite_count: 61, heart_count: 19 },
{ rank: 10, title: "基层治理数字化试点扩围,社会治理议题升温", url: "https://www.zhihu.com/question/100010", category_code: "society", category_label: "社会", heat_text: "4.4 万热度", heat_value: 44000, reply_count: 58, upvote_count: 301, favorite_count: 77, heart_count: 22 }
]
}
const themeMeta = {
gov_blue_gold: {
label: "政务蓝金",
palette: ["#35D4FF", "#D5B16A", "#5D8CFF", "#5FE5D7", "#87AFFF", "#F0CF92", "#70D5FF", "#5E9BFF", "#78E7D8", "#FFC978"]
},
tech_cyan_blue: {
label: "科技青蓝",
palette: ["#22D3EE", "#5B8CFF", "#73F0FF", "#7DF0CF", "#9EC4FF", "#B9F7FF", "#7CC5FF", "#B6B1FF", "#F0ABFC", "#8EF5E1"]
},
industry_ink_green: {
label: "工业墨绿",
palette: ["#74C69D", "#D1B26F", "#3DD6B5", "#9AF3D0", "#6DAEE0", "#B8E5D0", "#F1CD8A", "#8BD8B7", "#A7F3D0", "#C8E6B5"]
},
meeting_red_gold: {
label: "会议红金",
palette: ["#EF6A57", "#D79A43", "#FF8D7D", "#FFC78D", "#F7B267", "#FFB4A7", "#FFD08C", "#F89080", "#FFE0A9", "#ECA4A4"]
}
}
const DESIGN_WIDTH = 1920
const DESIGN_HEIGHT = 1080
const state = { payload: null, theme: "gov_blue_gold", colorMap: {} }
let barChart = null
let pieChart = null
function esc(value) {
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
}
function nn(value) {
const num = Number(value)
return Number.isFinite(num) ? num : 0
}
function fmtNum(value) {
return nn(value).toLocaleString("zh-CN")
}
function fmtTime(timestamp) {
if (!timestamp) return "-"
const date = new Date(timestamp)
if (Number.isNaN(date.getTime())) return "-"
return new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}).format(date)
}
function fmtCompact(value) {
const num = nn(value)
if (num >= 100000000) {
return (num / 100000000).toFixed(2).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1") + " 亿"
}
if (num >= 10000) {
return (num / 10000).toFixed(2).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1") + " 万"
}
return fmtNum(num)
}
function fmtHeat(text, value) {
const normalized = String(text || "").trim()
return normalized || fmtCompact(value)
}
function pct(part, total) {
if (!total) return "0%"
return ((part / total) * 100).toFixed(1).replace(/\.0$/, "") + "%"
}
function trunc(text, maxLength) {
const normalized = String(text || "")
return normalized.length <= maxLength ? normalized : normalized.slice(0, maxLength - 1) + "…"
}
function normalizeRow(row, index) {
return {
rank: nn(row && row.rank) || index + 1,
title: String((row && row.title) || "未命名话题"),
url: String((row && row.url) || "#"),
category_code: String((row && (row.category_code || row.category_label)) || "other").trim() || "other",
category_label: String((row && (row.category_label || row.category_code)) || "其他").trim() || "其他",
heat_text: String((row && row.heat_text) || ""),
heat_value: nn(row && row.heat_value),
reply_count: nn(row && row.reply_count),
upvote_count: nn(row && row.upvote_count),
favorite_count: nn(row && row.favorite_count),
heart_count: nn(row && row.heart_count)
}
}
function deriveCategories(table) {
const grouped = new Map()
table.forEach(function(row) {
const key = row.category_code
const current = grouped.get(key) || {
category_code: row.category_code,
category_label: row.category_label,
item_count: 0,
total_heat: 0,
avg_heat: 0
}
current.item_count += 1
current.total_heat += nn(row.heat_value)
grouped.set(key, current)
})
return Array.from(grouped.values()).map(function(item) {
return {
category_code: item.category_code,
category_label: item.category_label,
item_count: item.item_count,
total_heat: item.total_heat,
avg_heat: item.item_count ? Math.round(item.total_heat / item.item_count) : 0
}
})
}
function normalizeCategories(categories, table) {
const source = Array.isArray(categories) && categories.length ? categories : deriveCategories(table)
return source.map(function(item) {
return {
category_code: String((item && item.category_code) || "other").trim() || "other",
category_label: String((item && item.category_label) || "其他").trim() || "其他",
item_count: nn(item && item.item_count),
total_heat: nn(item && item.total_heat),
avg_heat: nn(item && item.avg_heat)
}
})
}
function normalizePayload(raw) {
const source = raw && raw.echarts && typeof raw.echarts === "object" ? raw.echarts : raw
if (!source || typeof source !== "object") {
throw new Error("invalid payload")
}
const table = Array.isArray(source.table) ? source.table.map(normalizeRow) : []
const categories = normalizeCategories(source.categories, table)
return {
snapshot_id: String(source.snapshot_id || "未命名快照"),
generated_at_ms: nn(source.generated_at_ms),
categories: categories,
table: table
}
}
function syncColors(categories) {
const map = Object.assign({}, state.colorMap)
const usedSlots = new Set(Object.values(map))
categories.forEach(function(category) {
if (map[category.category_code] !== undefined) return
let slot = 0
while (usedSlots.has(slot)) slot += 1
map[category.category_code] = slot
usedSlots.add(slot)
})
state.colorMap = map
}
function palette() {
return (themeMeta[state.theme] || themeMeta.gov_blue_gold).palette
}
function catColor(code) {
const slot = state.colorMap[code] || 0
return palette()[slot % palette().length]
}
function buildInsights(payload) {
const categories = payload.categories.slice().sort(function(a, b) {
return nn(b.total_heat) - nn(a.total_heat)
})
const rows = payload.table.slice().sort(function(a, b) {
return nn(b.heat_value) - nn(a.heat_value)
})
const totalHeat = categories.reduce(function(sum, item) { return sum + nn(item.total_heat) }, 0)
const topCategory = categories[0]
const hottest = rows[0]
const topThreeHeat = rows.slice(0, 3).reduce(function(sum, item) { return sum + nn(item.heat_value) }, 0)
return [
{
tag: "热点主轴",
text: topCategory
? topCategory.category_label + "类以 " + pct(topCategory.total_heat, totalHeat) + " 的热度占比居首,共覆盖 " + fmtNum(topCategory.item_count) + " 条热点。"
: "当前暂无分类数据。"
},
{
tag: "关注焦点",
text: hottest
? "最高热议题为“" + trunc(hottest.title, 28) + "”,当前热度 " + fmtHeat(hottest.heat_text, hottest.heat_value) + "。"
: "当前暂无热点明细。"
},
{
tag: "传播结构",
text: rows.length
? "前 3 条热点合计占总热度 " + pct(topThreeHeat, totalHeat) + ",适合汇报时优先讲解高关注议题与分类扩散态势。"
: "当前暂无传播结构数据。"
}
]
}
function renderSummary(payload) {
const rows = payload.table.slice().sort(function(a, b) {
return nn(b.heat_value) - nn(a.heat_value)
})
const categories = payload.categories.slice().sort(function(a, b) {
return nn(b.total_heat) - nn(a.total_heat)
})
const totalHeat = categories.reduce(function(sum, item) { return sum + nn(item.total_heat) }, 0)
const hottest = rows[0]
const topCategory = categories[0]
const cards = [
{
id: "kpi0",
label: "热榜条目数",
value: fmtNum(payload.table.length),
sub: "当前覆盖 " + fmtNum(payload.categories.length) + " 个分类"
},
{
id: "kpi1",
label: "总热度规模",
value: fmtCompact(totalHeat),
sub: topCategory ? topCategory.category_label + "类热度贡献最高" : "等待分类数据"
},
{
id: "kpi2",
label: "最高热话题",
value: hottest ? hottest.title : "-",
sub: hottest ? "热度 " + fmtHeat(hottest.heat_text, hottest.heat_value) : "等待热点明细",
long: true
},
{
id: "kpi3",
label: "主题分类数",
value: fmtNum(payload.categories.length),
sub: topCategory ? "首位分类占比 " + pct(topCategory.total_heat, totalHeat) : "等待分类结构"
}
]
cards.forEach(function(card) {
const element = document.getElementById(card.id)
if (!element) return
element.querySelector(".kpi-label").textContent = card.label
const valueEl = element.querySelector(".kpi-value")
valueEl.textContent = card.value
valueEl.title = card.value
valueEl.classList.toggle("long", !!card.long)
element.querySelector(".kpi-sub").textContent = card.sub
})
const notes = buildInsights(payload)
document.getElementById("summaryNotes").innerHTML = notes.map(function(item) {
return '<div class="summary-note"><div class="summary-note-tag">' + esc(item.tag) + '</div><div class="summary-note-text">' + esc(item.text) + '</div></div>'
}).join("")
}
function renderBar(payload) {
if (!barChart) return
const rows = payload.table.slice().sort(function(a, b) {
return nn(b.heat_value) - nn(a.heat_value)
}).slice(0, 10).reverse()
const styles = getComputedStyle(document.documentElement)
const text = styles.getPropertyValue("--text").trim()
const muted = styles.getPropertyValue("--muted").trim()
const line = styles.getPropertyValue("--line").trim()
const panelStrong = styles.getPropertyValue("--panel-strong").trim()
barChart.setOption({
animationDuration: 500,
animationEasing: "cubicOut",
backgroundColor: "transparent",
grid: { left: 8, right: 82, top: 8, bottom: 8, containLabel: true },
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" },
backgroundColor: panelStrong,
borderColor: line,
borderWidth: 1,
textStyle: { color: text, fontSize: 12 },
formatter: function(items) {
const item = items && items[0]
if (!item) return "-"
return esc(item.name) + "<br/>热度:" + fmtNum(item.value)
}
},
xAxis: { type: "value", show: false },
yAxis: {
type: "category",
data: rows.map(function(row) { return trunc(row.title, 16) }),
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
color: muted,
fontSize: 13,
width: 200,
overflow: "truncate"
}
},
series: [{
type: "bar",
barWidth: 18,
label: {
show: true,
position: "right",
color: text,
fontSize: 12,
fontFamily: "Bahnschrift, DIN Alternate, sans-serif",
formatter: function(item) { return fmtCompact(item.value) }
},
data: rows.map(function(row) {
const color = catColor(row.category_code)
return {
value: nn(row.heat_value),
name: row.title,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: color + "33" },
{ offset: 1, color: color }
]),
borderRadius: [0, 10, 10, 0]
}
}
})
}]
}, true)
}
function renderPie(payload) {
if (!pieChart) return
const categories = payload.categories.slice().sort(function(a, b) {
return nn(b.total_heat) - nn(a.total_heat)
})
const totalHeat = categories.reduce(function(sum, item) { return sum + nn(item.total_heat) }, 0)
const styles = getComputedStyle(document.documentElement)
const text = styles.getPropertyValue("--text").trim()
const muted = styles.getPropertyValue("--muted").trim()
const line = styles.getPropertyValue("--line").trim()
const panelStrong = styles.getPropertyValue("--panel-strong").trim()
pieChart.setOption({
animationDuration: 500,
animationEasing: "cubicOut",
backgroundColor: "transparent",
tooltip: {
trigger: "item",
backgroundColor: panelStrong,
borderColor: line,
borderWidth: 1,
textStyle: { color: text, fontSize: 12 },
formatter: function(item) {
return esc(item.name) + "<br/>热度:" + fmtNum(item.value) + "<br/>占比:" + item.percent + "%"
}
},
series: [{
type: "pie",
radius: ["46%", "72%"],
center: ["50%", "54%"],
avoidLabelOverlap: true,
itemStyle: {
borderColor: "rgba(4, 17, 34, 0.95)",
borderWidth: 2,
borderRadius: 8
},
label: { show: false },
emphasis: {
scale: true,
scaleSize: 8,
label: {
show: true,
color: text,
fontSize: 14,
formatter: function(item) {
return item.name + "\n" + item.percent + "%"
}
}
},
data: categories.map(function(item) {
return {
name: item.category_label,
value: nn(item.total_heat),
itemStyle: { color: catColor(item.category_code) }
}
})
}],
graphic: totalHeat ? [{
type: "group",
left: "center",
top: "center",
children: [
{
type: "text",
style: {
text: "分类热度",
fill: muted,
font: "14px Microsoft YaHei UI"
},
top: -6,
left: -28
},
{
type: "text",
style: {
text: fmtCompact(totalHeat),
fill: text,
font: "700 20px Bahnschrift"
},
top: 18,
left: -34
}
]
}] : []
}, true)
}
function renderCategoryBoard(payload) {
const categories = payload.categories.slice().sort(function(a, b) {
return nn(b.total_heat) - nn(a.total_heat)
})
const totalHeat = categories.reduce(function(sum, item) { return sum + nn(item.total_heat) }, 0)
document.getElementById("categoryBoard").innerHTML = categories.map(function(item) {
const color = catColor(item.category_code)
return '<div class="category-row">'
+ '<div class="category-dot" style="color:' + esc(color) + ';background:' + esc(color) + '"></div>'
+ '<div class="category-name">' + esc(item.category_label) + '</div>'
+ '<div class="category-metrics">'
+ '占比 ' + esc(pct(item.total_heat, totalHeat)) + '<br/>均值 ' + esc(fmtCompact(item.avg_heat))
+ '</div>'
+ '</div>'
}).join("")
}
function rankClass(rank) {
if (rank === 1) return "rank-chip top-1"
if (rank === 2) return "rank-chip top-2"
if (rank === 3) return "rank-chip top-3"
return "rank-chip"
}
function renderTablePane(rows) {
return '<div class="table-pane"><table><thead><tr><th>排名</th><th>标题</th><th>类别</th><th>热度</th></tr></thead><tbody>'
+ rows.map(function(row) {
const color = catColor(row.category_code)
return '<tr>'
+ '<td><span class="' + rankClass(row.rank) + '">' + esc(row.rank) + '</span></td>'
+ '<td class="title-cell" title="' + esc(row.title) + '">' + esc(row.title) + '</td>'
+ '<td><span class="category-tag" style="color:' + esc(color) + ';border-color:' + esc(color) + ';">' + esc(row.category_label) + '</span></td>'
+ '<td>' + esc(fmtHeat(row.heat_text, row.heat_value)) + '</td>'
+ '</tr>'
}).join("")
+ '</tbody></table></div>'
}
function renderTable(payload) {
const wrap = document.getElementById("tableWrap")
const rows = payload.table.slice().sort(function(a, b) { return a.rank - b.rank })
if (!rows.length) {
wrap.style.setProperty("--table-columns", "1")
wrap.innerHTML = '<div class="table-empty">暂无明细数据</div>'
document.getElementById("tableMeta").textContent = "暂无可展示的热榜数据"
return
}
const maxRowsPerColumn = 10
const columnCount = Math.max(1, Math.min(3, Math.ceil(rows.length / maxRowsPerColumn)))
const rowsPerColumn = Math.ceil(rows.length / columnCount)
wrap.style.setProperty("--table-columns", String(columnCount))
const panes = []
for (let i = 0; i < columnCount; i += 1) {
const start = i * rowsPerColumn
const end = start + rowsPerColumn
panes.push(renderTablePane(rows.slice(start, end)))
}
wrap.innerHTML = panes.join("")
document.getElementById("tableMeta").textContent = "共 " + fmtNum(rows.length) + " 条数据,按 " + columnCount + " 栏压缩排布,确保一屏展示完整。"
}
function updateMeta(payload) {
document.getElementById("snapshotId").textContent = payload.snapshot_id || "-"
document.getElementById("generatedAt").textContent = fmtTime(payload.generated_at_ms)
document.getElementById("footerGeneratedAt").textContent = fmtTime(payload.generated_at_ms)
document.getElementById("footerTheme").textContent = (themeMeta[state.theme] || themeMeta.gov_blue_gold).label
}
function renderAll(payload) {
state.payload = payload
syncColors(payload.categories)
renderSummary(payload)
renderBar(payload)
renderPie(payload)
renderCategoryBoard(payload)
renderTable(payload)
updateMeta(payload)
}
function updateThemeButtons() {
document.querySelectorAll(".theme-btn").forEach(function(button) {
button.classList.toggle("active", button.dataset.theme === state.theme)
})
}
function setTheme(themeName) {
if (!themeMeta[themeName]) return
state.theme = themeName
document.documentElement.dataset.theme = themeName
try {
localStorage.setItem("zhihu-hotlist-theme", themeName)
} catch (error) {}
updateThemeButtons()
if (state.payload) {
renderAll(state.payload)
}
requestAnimationFrame(function() {
if (barChart) barChart.resize()
if (pieChart) pieChart.resize()
})
}
function fitScreenToViewport() {
const canvas = document.getElementById("dashboardCanvas")
if (!canvas) return
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
const scale = Math.min(viewportWidth / DESIGN_WIDTH, viewportHeight / DESIGN_HEIGHT)
canvas.style.transform = 'translate(-50%, -50%) scale(' + scale + ')'
if (barChart) barChart.resize()
if (pieChart) pieChart.resize()
}
function startClock() {
const element = document.getElementById("currentTime")
const tick = function() {
element.textContent = fmtTime(Date.now())
}
tick()
window.setInterval(tick, 1000)
}
function init() {
const storedTheme = (function() {
try {
return localStorage.getItem("zhihu-hotlist-theme")
} catch (error) {
return null
}
})()
if (storedTheme && themeMeta[storedTheme]) {
state.theme = storedTheme
document.documentElement.dataset.theme = storedTheme
}
updateThemeButtons()
if (window.echarts) {
barChart = echarts.init(document.getElementById("barChart"))
pieChart = echarts.init(document.getElementById("pieChart"))
}
document.querySelectorAll(".theme-btn").forEach(function(button) {
button.addEventListener("click", function() {
setTheme(button.dataset.theme)
})
})
window.addEventListener("resize", fitScreenToViewport)
startClock()
renderAll(normalizePayload(defaultPayload))
fitScreenToViewport()
}
init()
</script>
</body>
</html>