async function openEntityDetail(item) { state.entityDetailItem = item; state.entityDetailPayload = null; state.entityDetailIntraday = null; state.entityDetailChartMode = "daily"; const requestSequence = ++state.entityDetailRequestSequence; syncDetailChartButtons("entity", "daily"); setText("entityDetailCode", item.code || item.id || "--"); setText("entityDetailName", item.name || "--"); setText("entityDetailValue", "--"); setText("entityDetailChange", "--"); setText("entityDetailType", item.type_label || "--"); setText("entityDetailDate", "正在加载行情"); document.querySelector("#entityDetailChange").className = ""; renderEmptyState("entityDetailMetrics", "正在加载交易数据"); openModalDialog(elements.entityDetailDialog); clearEntityDetailChart("正在加载日 K 数据"); try { const params = new URLSearchParams({ type: item.type, id: item.id, trade_date: elements.tradeDate.value }); const payload = await apiRequest(`/api/search/detail?${params}`); if (requestSequence !== state.entityDetailRequestSequence) return; state.entityDetailPayload = payload; const entity = payload.entity || {}; setText("entityDetailCode", entity.code || item.code || "--"); setText("entityDetailName", entity.name || item.name || "--"); setText("entityDetailValue", meaningfulNumber(entity.value) && number(entity.value) !== 0 ? formatNumber(entity.value, 2) : "--"); setText("entityDetailChange", `${signed(entity.change)}%`); setText("entityDetailType", entity.type_label || item.type_label || "--"); document.querySelector("#entityDetailChange").className = changeClass(entity.change); renderEntityDetailMetrics(payload.metrics || []); if (state.entityDetailChartMode === "daily") { setText("entityDetailDate", `${payload.meta?.realtime ? "实时" : "收盘"} · ${payload.meta?.trade_date || "--"}`); requestAnimationFrame(() => drawEntityDetailChart(payload.series || [])); } } catch (error) { if (requestSequence !== state.entityDetailRequestSequence) return; setText("entityDetailDate", "行情加载失败"); renderEmptyState("entityDetailMetrics", error.message || "交易数据加载失败"); if (state.entityDetailChartMode === "daily") clearEntityDetailChart(error.message || "行情加载失败"); showToast(error.message || "详情加载失败"); } } async function selectEntityDetailChart(mode) { const selected = mode === "intraday" ? "intraday" : "daily"; state.entityDetailChartMode = selected; syncDetailChartButtons("entity", selected); if (selected === "daily") { const payload = state.entityDetailPayload; if (payload) { setText("entityDetailDate", `${payload.meta?.realtime ? "实时" : "收盘"} · ${payload.meta?.trade_date || "--"}`); requestAnimationFrame(() => drawEntityDetailChart(payload.series || [])); } else clearEntityDetailChart("正在加载日 K 数据"); return; } if (state.entityDetailIntraday) { renderEntityIntraday(state.entityDetailIntraday); return; } const item = state.entityDetailItem; if (!item) return; const requestSequence = state.entityDetailRequestSequence; setText("entityDetailDate", "正在加载分时"); clearEntityDetailChart("正在加载分时数据"); try { const params = new URLSearchParams({ type: item.type, id: item.id }); const payload = await apiRequest(`/api/chart/intraday?${params}`); if (requestSequence !== state.entityDetailRequestSequence) return; state.entityDetailIntraday = payload; if (state.entityDetailChartMode === "intraday") renderEntityIntraday(payload); } catch (error) { if (requestSequence !== state.entityDetailRequestSequence || state.entityDetailChartMode !== "intraday") return; setText("entityDetailDate", "分时暂不可用"); clearEntityDetailChart(error.message || "分时行情暂不可用"); } } function renderEntityIntraday(payload) { const points = payload.points || []; if (!points.length) { setText("entityDetailDate", "分时暂不可用"); clearEntityDetailChart("分时行情暂不可用"); return; } setText("entityDetailDate", `分时 · ${payload.meta?.trade_date || "--"}`); requestAnimationFrame(() => { if (state.entityDetailChartMode !== "intraday") return; drawIntradayCanvas(elements.entityDetailChart, points, [], payload.meta?.previous_close); }); } function syncDetailChartButtons(scope, mode) { const selector = scope === "stock" ? "[data-stock-detail-chart]" : "[data-entity-detail-chart]"; const datasetKey = scope === "stock" ? "stockDetailChart" : "entityDetailChart"; document.querySelectorAll(selector).forEach((button) => { const active = button.dataset[datasetKey] === mode; button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); }); } function renderEntityDetailMetrics(metrics) { const container = document.querySelector("#entityDetailMetrics"); if (!metrics.length) { renderEmptyState(container, "暂无交易数据"); return; } container.innerHTML = metrics.map((metric) => { const value = typeof metric.value === "number" ? formatNumber(metric.value, Number.isInteger(metric.value) ? 0 : 2) : String(metric.value ?? "--"); const tone = metric.tone === "change" ? changeClass(metric.value) : ""; return `