125 lines
5.6 KiB
JavaScript
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);
|
|
}
|