Files
xiaobaifupan/app/frontend/pages/market/stock-detail.js
T

125 lines
5.6 KiB
JavaScript

function meaningfulNumber(value) {
return value !== null && value !== undefined && value !== "" && Number.isFinite(Number(value));
}
async function openStock(code, fallback = null) {
closeStockPreview();
const pools = [state.dashboard?.limits || [], state.dashboard?.broken || [], state.dashboard?.down_limits || []];
const row = pools.flat().find((item) => String(item.code) === String(code)) || fallback || { code, name: "--", sector: "其他" };
state.activeStock = row;
state.stockDetail = null;
state.stockDetailIntraday = null;
state.stockDetailChartMode = "daily";
const requestSequence = ++state.stockDetailRequestSequence;
syncDetailChartButtons("stock", "daily");
setText("detailCode", row.code);
setText("detailName", row.name);
setText("detailPrice", formatNumber(row.price, 2));
setText("detailChange", `${signed(row.change)}%`);
const changeElement = document.querySelector("#detailChange");
changeElement.className = changeClass(row.change);
setText("detailStreak", row.status === "涨停" ? streakLabel(row.streak) : row.status || "--");
setText("detailReason", row.reason || "--");
setText("detailSector", row.sector || "其他");
setText("detailFirst", row.first_time || "--");
setText("detailLast", row.last_time || "--");
setText("detailOpen", `${number(row.open_times)} 次`);
setText("detailTurnover", `${formatNumber(row.turnover_rate, 2)}%`);
setText("detailAmount", `${formatNumber(row.amount_billion, 2)} 亿`);
setText("detailSeal", `${formatNumber(row.seal_amount_million, 0)} 万`);
setText("chartSource", "正在加载行情");
setText("flowNet", "--");
setText("flowLarge", "--");
setText("flowMedium", "--");
setText("flowSmall", "--");
document.querySelector("#reasonInput").value = row.reason || "";
document.querySelector("#stockNoteContent").value = "";
document.querySelector("#stockNotePlan").value = "";
renderEmptyState("stockNotes", "正在加载笔记");
updateWatchButton();
openModalDialog(elements.stockDialog);
clearPriceChart("正在加载日 K 数据");
try {
const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
const payload = await apiRequest(`/api/stock/${encodeURIComponent(code)}?${query}`);
if (requestSequence !== state.stockDetailRequestSequence) return;
state.stockDetail = payload;
const stock = payload.stock || {};
state.activeStock = { ...row, name: stock.name || row.name, sector: stock.industry || row.sector };
setText("detailName", stock.name || row.name);
setText("detailPrice", formatNumber(stock.price || row.price, 2));
setText("detailChange", `${signed(stock.change ?? row.change)}%`);
renderMoneyflow(payload.moneyflow || {});
renderStockNotes(payload.notes || []);
updateWatchButton();
if (state.stockDetailChartMode === "daily") {
setText("chartSource", `日 K 行情 · ${payload.prices.length} 个交易日`);
requestAnimationFrame(() => drawPriceChart(payload.prices || []));
}
} catch (error) {
if (requestSequence !== state.stockDetailRequestSequence) return;
setText("chartSource", "行情加载失败");
if (state.stockDetailChartMode === "daily") clearPriceChart(error.message || "行情加载失败");
showToast(error.message || "个股详情加载失败");
}
}
async function selectStockDetailChart(mode) {
const selected = mode === "intraday" ? "intraday" : "daily";
state.stockDetailChartMode = selected;
syncDetailChartButtons("stock", selected);
if (selected === "daily") {
const prices = state.stockDetail?.prices || [];
setText("chartSource", prices.length ? `日 K 行情 · ${prices.length} 个交易日` : "正在加载行情");
if (prices.length) requestAnimationFrame(() => drawPriceChart(prices));
else clearPriceChart("正在加载日 K 数据");
return;
}
if (state.stockDetailIntraday) {
renderStockDetailIntraday(state.stockDetailIntraday);
return;
}
const code = String(state.activeStock?.code || "");
if (!/^\d{6}$/.test(code)) return;
const requestSequence = state.stockDetailRequestSequence;
setText("chartSource", "正在加载分时");
clearPriceChart("正在加载分时数据");
try {
const params = new URLSearchParams({ type: "stock", id: code });
const payload = await apiRequest(`/api/chart/intraday?${params}`);
if (requestSequence !== state.stockDetailRequestSequence) return;
state.stockDetailIntraday = payload;
if (state.stockDetailChartMode === "intraday") renderStockDetailIntraday(payload);
} catch (error) {
if (requestSequence !== state.stockDetailRequestSequence || state.stockDetailChartMode !== "intraday") return;
setText("chartSource", "分时暂不可用");
clearPriceChart(error.message || "分时行情暂不可用");
}
}
function renderStockDetailIntraday(payload) {
const points = payload.points || [];
if (!points.length) {
setText("chartSource", "分时暂不可用");
clearPriceChart("分时行情暂不可用");
return;
}
setText("chartSource", `分时 · ${payload.meta?.trade_date || "--"}`);
requestAnimationFrame(() => {
if (state.stockDetailChartMode !== "intraday") return;
drawIntradayCanvas(elements.priceChart, points, [], payload.meta?.previous_close);
});
}
function openActiveStockInHeaven() {
const code = state.activeStock?.code;
if (!/^\d{6}$/.test(String(code || ""))) return;
elements.stockDialog.close();
state.heavenPanel = "trend";
state.heavenManualData = null;
const input = document.querySelector("#heavenStockInput");
input.value = code;
openView("heavenView");
selectHeavenPanel("trend", true);
}