let globalSearchTimer = null;
function openGlobalSearch() {
if (!state.user) return;
toggleHeaderCommandMenu(false);
openModalDialog(elements.globalSearchDialog);
requestAnimationFrame(() => {
elements.globalSearchInput.focus();
elements.globalSearchInput.select();
});
}
function handleGlobalSearchShortcut(event) {
if (!event.ctrlKey || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
if (!state.user) return;
if (event.defaultPrevented) {
showToast("Ctrl+K 已被其他功能占用,请点击顶部搜索按钮");
return;
}
event.preventDefault();
openGlobalSearch();
}
function closeGlobalSearch() {
clearTimeout(globalSearchTimer);
if (elements.globalSearchDialog.open) elements.globalSearchDialog.close();
}
function scheduleGlobalSearch() {
clearTimeout(globalSearchTimer);
const query = elements.globalSearchInput.value.trim();
state.globalSearchActiveIndex = -1;
if (!query) {
state.globalSearchResults = [];
renderGlobalSearchEmpty("输入名称或代码开始搜索", "使用方向键选择,回车打开详情", "corner-down-left");
return;
}
elements.globalSearchResults.innerHTML = '
正在搜索
';
globalSearchTimer = setTimeout(() => runGlobalSearch(query), 160);
}
async function runGlobalSearch(query) {
const requestSequence = ++state.globalSearchRequestSequence;
const params = new URLSearchParams({ q: query, trade_date: elements.tradeDate.value });
try {
const payload = await apiRequest(`/api/search?${params}`);
if (requestSequence !== state.globalSearchRequestSequence || elements.globalSearchInput.value.trim() !== query) return;
renderGlobalSearchResults(payload.groups || {});
} catch (error) {
if (requestSequence !== state.globalSearchRequestSequence) return;
state.globalSearchResults = [];
renderGlobalSearchEmpty(error.message || "搜索失败", "请稍后重试", "circle-alert");
}
}
function renderGlobalSearchResults(groups) {
const definitions = [
["stocks", "股票"],
["sectors", "板块"],
["themes", "题材"],
["indices", "指数"],
];
const iconNames = { stock: "chart-candlestick", sector: "layout-grid", theme: "lightbulb", index: "chart-line" };
const flattened = [];
const sections = [];
definitions.forEach(([key, label]) => {
const items = Array.isArray(groups[key]) ? groups[key] : [];
if (!items.length) return;
const rows = items.map((item) => {
const index = flattened.length;
flattened.push(item);
return ``;
}).join("");
sections.push(``);
});
state.globalSearchResults = flattened;
state.globalSearchActiveIndex = flattened.length ? 0 : -1;
if (!flattened.length) {
renderGlobalSearchEmpty("没有找到相关结果", "可尝试输入完整名称或六位股票代码", "search-x");
return;
}
elements.globalSearchResults.innerHTML = sections.join("");
updateGlobalSearchSelection(false);
refreshIcons();
}
function renderGlobalSearchEmpty(title, hint, iconName) {
elements.globalSearchResults.innerHTML = `${escapeHtml(title)}
${escapeHtml(hint)} `;
refreshIcons();
}
function handleGlobalSearchInputKeydown(event) {
if (event.key === "Escape") {
event.preventDefault();
closeGlobalSearch();
return;
}
if (!["ArrowDown", "ArrowUp", "Enter"].includes(event.key)) return;
if (!state.globalSearchResults.length) return;
event.preventDefault();
if (event.key === "Enter") {
openGlobalSearchResult(state.globalSearchActiveIndex);
return;
}
const direction = event.key === "ArrowDown" ? 1 : -1;
state.globalSearchActiveIndex = (state.globalSearchActiveIndex + direction + state.globalSearchResults.length) % state.globalSearchResults.length;
updateGlobalSearchSelection(true);
}
function updateGlobalSearchSelection(scrollIntoView) {
elements.globalSearchResults.querySelectorAll("[data-search-result-index]").forEach((item) => {
const selected = number(item.dataset.searchResultIndex) === state.globalSearchActiveIndex;
item.classList.toggle("is-active", selected);
item.setAttribute("aria-selected", String(selected));
if (selected && scrollIntoView) item.scrollIntoView({ block: "nearest" });
});
}
function openGlobalSearchResult(index) {
const item = state.globalSearchResults[index];
if (!item) return;
closeGlobalSearch();
if (item.type === "stock") {
openStock(item.id, { code: item.code, name: item.name, sector: item.industry || "其他" });
return;
}
openEntityDetail(item);
}