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>
1432 lines
50 KiB
HTML
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, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/\"/g, """)
|
|
}
|
|
|
|
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>
|