101 lines
5.8 KiB
JavaScript
101 lines
5.8 KiB
JavaScript
window.XiaobaiPageModules.register("popularity", ["popularityView"], {
|
||
bind: bindPopularityEvents,
|
||
enter: ["loadPopularity"],
|
||
});
|
||
|
||
async function loadPopularity(force = false) {
|
||
if (state.popularityLoading) return;
|
||
state.popularityLoading = true;
|
||
const button = document.querySelector("#popularityRefreshButton");
|
||
button.disabled = true;
|
||
setText("popularityDateLabel", "正在读取人气榜");
|
||
try {
|
||
const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
|
||
if (force) query.set("force", "1");
|
||
state.popularityData = await apiRequest(`/api/popularity?${query}`);
|
||
renderPopularity();
|
||
} catch (error) {
|
||
setText("popularityDateLabel", error.message || "人气榜暂不可用");
|
||
document.querySelector("#popularityTableBody").innerHTML = "";
|
||
document.querySelector("#popularityEmpty").hidden = false;
|
||
showToast(error.message || "人气榜加载失败");
|
||
} finally {
|
||
state.popularityLoading = false;
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
function renderPopularity() {
|
||
const payload = state.popularityData;
|
||
if (!payload) return;
|
||
const summary = payload.summary || {};
|
||
setText("popularityDateLabel", `${payload.meta?.carried_forward ? "最近有效榜单" : "榜单日期"} ${payload.meta?.trade_date || "--"}`);
|
||
const topNames = (rows) => (rows || []).slice(0, 3).map((item) => item.name).filter(Boolean).join(" · ") || "--";
|
||
document.querySelector("#popularitySummary").innerHTML = [
|
||
["同花顺热度 Top3", topNames(payload.ths), `共 ${number(summary.ths_count)} 只上榜`],
|
||
["东方财富热度 Top3", topNames(payload.dc), `共 ${number(summary.dc_count)} 只上榜`],
|
||
["双榜共识", `${number(summary.dual_count)} 只`, "同时进入两榜,共识度更高"],
|
||
].map(([label, value, detail], index) => `<article class="${index === 2 ? "consensus" : ""}"><span>${label}</span><strong>${escapeHtml(value)}</strong><small>${escapeHtml(detail)}</small></article>`).join("");
|
||
renderPopularityTable();
|
||
}
|
||
|
||
function renderPopularityTable() {
|
||
const source = state.popularitySource;
|
||
let rows = [...(state.popularityData?.[source] || [])];
|
||
if (state.popularityQuery) {
|
||
rows = rows.filter((item) => `${item.code} ${item.name} ${(item.concepts || []).join(" ")}`.toLocaleLowerCase("zh-CN").includes(state.popularityQuery));
|
||
}
|
||
const combined = source === "combined";
|
||
const sourceName = source === "ths" ? "同花顺" : source === "dc" ? "东方财富" : "双榜综合";
|
||
setText("popularityTableTitle", `${sourceName}榜`);
|
||
setText("popularityTableNote", combined ? "按双榜排名综合排序 · 已隐藏重复的榜单状态" : "按榜单名次排序 · 状态显示是否同时进入另一榜");
|
||
const headers = [
|
||
["排名", "number num"], ["股票", ""], ["最新价(元)", "number num"], ["涨跌幅(%)", "number num"],
|
||
...(source !== "dc" ? [["同花顺", "number num"]] : []),
|
||
...(source !== "ths" ? [["东方财富", "number num"]] : []),
|
||
["排名变化", "number num"], ["热门概念", ""], ...(!combined ? [["榜单状态", ""]] : []),
|
||
];
|
||
document.querySelector("#popularityTableHead").innerHTML = headers.map(([label, className]) => `<th scope="col" class="${className}">${label}</th>`).join("");
|
||
const body = document.querySelector("#popularityTableBody");
|
||
body.innerHTML = rows.map((row, index) => {
|
||
const thsRank = source === "ths" ? row.rank : row.ths_rank;
|
||
const dcRank = source === "dc" ? row.rank : row.dc_rank;
|
||
const move = row.rank_change;
|
||
const movement = move === null || move === undefined ? "新" : number(move) > 0 ? `↑${number(move)}` : number(move) < 0 ? `↓${Math.abs(number(move))}` : "持平";
|
||
return `<tr data-code="${escapeHtml(row.code)}">
|
||
<td class="number num popularity-rank-v2"><b>${index + 1}</b>${index < 3 ? '<span>热</span>' : ""}</td>
|
||
<td><div class="popularity-stock-v2"><strong class="stock-name sname">${escapeHtml(row.name)}</strong><span class="stock-code scode">${escapeHtml(row.code)}</span></div></td>
|
||
<td class="number num">${row.price == null ? "" : formatNumber(row.price, 2)}</td>
|
||
<td class="number num ${row.change == null ? "" : changeClass(row.change)}">${row.change == null ? "" : signed(row.change)}</td>
|
||
${source !== "dc" ? `<td class="number num popularity-list-rank-v2">${thsRank ? number(thsRank) : ""}</td>` : ""}
|
||
${source !== "ths" ? `<td class="number num popularity-list-rank-v2">${dcRank ? number(dcRank) : ""}</td>` : ""}
|
||
<td class="number num popularity-movement-v2 ${number(move) > 0 ? "up" : number(move) < 0 ? "down" : ""}">${movement}</td>
|
||
<td class="popularity-concepts-v2" title="${escapeHtml((row.concepts || []).join("、"))}">${escapeHtml((row.concepts || []).slice(0, 3).join("、"))}</td>
|
||
${!combined ? `<td><span class="popularity-source-tag-v2 ${row.dual_source ? "dual" : ""}">${row.dual_source ? "双榜共识" : "单榜入选"}</span></td>` : ""}
|
||
</tr>`;
|
||
}).join("");
|
||
bindStockRows(body);
|
||
markAutoSortableHeaders(body.closest("table"));
|
||
document.querySelector("#popularityEmpty").hidden = rows.length > 0;
|
||
}
|
||
|
||
|
||
function bindPopularityEvents() {
|
||
document.querySelector("#popularityRefreshButton").addEventListener("click", () => loadPopularity(true));
|
||
document.querySelector("#popularitySearch").addEventListener("input", (event) => {
|
||
state.popularityQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
|
||
renderPopularityTable();
|
||
});
|
||
document.querySelectorAll("[data-popularity-source]").forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
state.popularitySource = button.dataset.popularitySource || "combined";
|
||
document.querySelectorAll("[data-popularity-source]").forEach((item) => {
|
||
const active = item === button;
|
||
item.classList.toggle("active", active);
|
||
item.setAttribute("aria-selected", String(active));
|
||
});
|
||
renderPopularityTable();
|
||
});
|
||
});
|
||
}
|