Skip to content

Commit a3f07ce

Browse files
committed
feat: add privacy-friendly visitor counter
1 parent 7b007bd commit a3f07ce

5 files changed

Lines changed: 80 additions & 9 deletions

File tree

catalog/api-catalog.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"catalogVersion": "1.15.2",
3+
"catalogVersion": "1.15.3",
44
"title": "WQ API 非官方接口目录",
55
"description": "以 WorldQuant BRAIN 前端 bundle 静态分析为基线,并通过人工复核的去敏真实响应逐项升级。此目录不是官方 API 承诺;unknown/inferred 字段必须在取得去敏真实响应后再升级。",
66
"generatedFrom": {

site/assets/app.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const DOMAIN_LABELS = {
1919

2020
const DOMAIN_ORDER = Object.keys(DOMAIN_LABELS);
2121
const BASE_URL = "https://api.worldquantbrain.com";
22+
const GOATCOUNTER_TOTAL_URL = "https://huahua.goatcounter.com/counter/TOTAL.json";
2223

2324
const SENSITIVITY_DESCRIPTIONS = {
2425
none: "未发现额外的敏感信息或高风险副作用。",
@@ -58,6 +59,8 @@ const elements = typeof document === "undefined" ? {} : {
5859
catalogStatus: document.querySelector("#catalog-status"),
5960
statusDot: document.querySelector(".status-dot"),
6061
downloadMarkdown: document.querySelector("#download-markdown"),
62+
visitorStat: document.querySelector("#visitor-stat"),
63+
visitorCount: document.querySelector("#visitor-count"),
6164
docPane: document.querySelector("#doc-pane"),
6265
parameterForm: document.querySelector("#parameter-form"),
6366
parameterSummary: document.querySelector("#parameter-summary"),
@@ -1341,6 +1344,23 @@ function downloadVisibleCatalogMarkdown() {
13411344
showToast(`${visibleOperations().length} 个接口的 Markdown 已生成`);
13421345
}
13431346

1347+
async function loadVisitorCount() {
1348+
try {
1349+
const response = await fetch(GOATCOUNTER_TOTAL_URL, {
1350+
cache: "no-store",
1351+
credentials: "omit"
1352+
});
1353+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
1354+
const payload = await response.json();
1355+
const count = String(payload.count ?? "").trim();
1356+
if (!/^\d[\d,.\s]*$/.test(count)) throw new Error("invalid visitor count");
1357+
elements.visitorCount.textContent = count;
1358+
elements.visitorStat.hidden = false;
1359+
} catch {
1360+
elements.visitorStat.hidden = true;
1361+
}
1362+
}
1363+
13441364
function bindEvents() {
13451365
elements.domainNav.addEventListener("click", (event) => {
13461366
const button = event.target.closest("[data-domain]");
@@ -1415,6 +1435,7 @@ function bindEvents() {
14151435

14161436
async function initialize() {
14171437
bindEvents();
1438+
void loadVisitorCount();
14181439
try {
14191440
const response = await fetch("./data/api-catalog.json", { cache: "no-cache" });
14201441
if (!response.ok) throw new Error(`HTTP ${response.status}`);

site/assets/styles.css

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
--sans: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
1919
--shadow: 0 12px 34px rgba(30, 39, 55, 0.08);
2020
--topbar-height: 72px;
21+
--footer-height: 30px;
2122
}
2223

2324
* {
@@ -260,7 +261,29 @@ kbd {
260261
.workspace {
261262
display: grid;
262263
grid-template-columns: 288px minmax(420px, 1fr) minmax(410px, 32vw);
263-
height: calc(100vh - var(--topbar-height));
264+
height: calc(100vh - var(--topbar-height) - var(--footer-height));
265+
}
266+
267+
.site-footer {
268+
display: flex;
269+
height: var(--footer-height);
270+
align-items: center;
271+
justify-content: center;
272+
border-top: 1px solid var(--line);
273+
background: var(--paper-raised);
274+
color: var(--ink-muted);
275+
font-family: var(--mono);
276+
font-size: 10px;
277+
letter-spacing: 0.025em;
278+
}
279+
280+
.visitor-stat[hidden] {
281+
display: none;
282+
}
283+
284+
.visitor-stat strong {
285+
color: var(--accent);
286+
font-weight: 750;
264287
}
265288

266289
.nav-pane,
@@ -1286,13 +1309,13 @@ noscript {
12861309
.workspace {
12871310
grid-template-columns: 235px minmax(0, 1fr);
12881311
height: auto;
1289-
min-height: calc(100vh - var(--topbar-height));
1312+
min-height: calc(100vh - var(--topbar-height) - var(--footer-height));
12901313
}
12911314

12921315
.nav-pane {
12931316
position: sticky;
12941317
top: 0;
1295-
height: calc(100vh - var(--topbar-height));
1318+
height: calc(100vh - var(--topbar-height) - var(--footer-height));
12961319
}
12971320

12981321
.doc-pane,
@@ -1336,6 +1359,10 @@ noscript {
13361359
display: block;
13371360
}
13381361

1362+
.site-footer {
1363+
position: relative;
1364+
}
1365+
13391366
.nav-pane {
13401367
position: static;
13411368
height: auto;

site/index.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<title>WQ API Workbench</title>
1111
<link rel="icon" type="image/png" sizes="32x32" href="./assets/favicon-32.png?v=ac9ae6d6875f" />
1212
<link rel="apple-touch-icon" href="./assets/quantapi-logo.png?v=19fd43f07782" />
13-
<link rel="stylesheet" href="./assets/styles.css?v=ad6ae40216fa" />
13+
<link rel="stylesheet" href="./assets/styles.css?v=0ea64f985da7" />
1414
</head>
1515
<body>
1616
<header class="topbar">
@@ -111,8 +111,15 @@ <h2 id="code-language-title">Python requests</h2>
111111
</aside>
112112
</div>
113113

114+
<footer class="site-footer" aria-label="网站访问统计">
115+
<span class="visitor-stat" id="visitor-stat" hidden>
116+
累计访问 <strong id="visitor-count" aria-live="polite"></strong>
117+
</span>
118+
</footer>
119+
114120
<div id="toast" class="toast" role="status" aria-live="polite"></div>
115121
<noscript>此页面需要 JavaScript 才能读取接口目录并生成请求代码。</noscript>
116-
<script type="module" src="./assets/app.js?v=b9a62316b7f3"></script>
122+
<script data-goatcounter="https://huahua.goatcounter.com/count" async src="https://gc.zgo.at/count.js"></script>
123+
<script type="module" src="./assets/app.js?v=d6737bb99c1c"></script>
117124
</body>
118125
</html>

tools/verify-site.mjs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ check(
109109
&& app.includes('elements.downloadMarkdown.disabled = false')
110110
&& css.includes(".catalog-download")
111111
);
112+
check(
113+
"页脚使用 GoatCounter 统计并安全显示累计访问量",
114+
html.includes('<footer class="site-footer" aria-label="网站访问统计">')
115+
&& html.includes('id="visitor-stat" hidden')
116+
&& html.includes('id="visitor-count" aria-live="polite"')
117+
&& html.includes('data-goatcounter="https://huahua.goatcounter.com/count"')
118+
&& html.includes('async src="https://gc.zgo.at/count.js"')
119+
&& app.includes('const GOATCOUNTER_TOTAL_URL = "https://huahua.goatcounter.com/counter/TOTAL.json"')
120+
&& app.includes('credentials: "omit"')
121+
&& app.includes("elements.visitorStat.hidden = false")
122+
&& app.includes("elements.visitorStat.hidden = true")
123+
&& css.includes("--footer-height: 30px")
124+
&& css.includes("calc(100vh - var(--topbar-height) - var(--footer-height))")
125+
&& !html.includes("goatcounter.com/api/")
126+
&& !app.includes("Authorization: Bearer")
127+
);
112128
check("页面已移除 QUICK TEST 与本地测试器", !html.includes("QUICK TEST") && !html.includes("LOCAL ONLY") && !app.includes("LocalTester"));
113129
check(
114130
"右侧代码面板使用统一浅色主题",
@@ -761,8 +777,8 @@ const superSelectionOperation = catalog.operations.find((operation) => operation
761777
const createProtoUserOperation = catalog.operations.find((operation) => operation.operationId === "createProtoUser");
762778
const searchPlatformOperation = catalog.operations.find((operation) => operation.operationId === "searchPlatform");
763779
check(
764-
"catalog 1.15.2 固化本轮实测覆盖率与剩余接口",
765-
catalog.catalogVersion === "1.15.2"
780+
"catalog 1.15.3 固化本轮实测覆盖率与剩余接口",
781+
catalog.catalogVersion === "1.15.3"
766782
&& visibleOperations.length === 93
767783
&& liveConfirmedVisibleOperations.length === 92
768784
&& JSON.stringify(remainingVisibleOperationIds) === JSON.stringify(["searchPlatform"])
@@ -779,7 +795,7 @@ const hiddenMarkdownSections = hiddenOperations.filter((operation) =>
779795
);
780796
check(
781797
"Markdown 下载内容完整且排除隐藏接口",
782-
visibleCatalogMarkdown.startsWith("# WQ API Catalog 1.15.2")
798+
visibleCatalogMarkdown.startsWith("# WQ API Catalog 1.15.3")
783799
&& visibleCatalogMarkdown.includes("共 93 个前端可见接口")
784800
&& visibleMarkdownSections.length === visibleOperations.length
785801
&& hiddenMarkdownSections.length === 0

0 commit comments

Comments
 (0)