feat: complete strategy and market data improvements
This commit is contained in:
+185
-40
@@ -41,7 +41,7 @@ const state = {
|
||||
yesterdayQuery: "",
|
||||
yesterdaySortKey: "",
|
||||
yesterdaySortDirection: "desc",
|
||||
activeView: "limitPool",
|
||||
activeView: "sentimentCycleView",
|
||||
dragonTiger: null,
|
||||
dragonViewMode: "daily",
|
||||
dragonFilter: "all",
|
||||
@@ -89,6 +89,8 @@ const state = {
|
||||
entityDetailIntraday: null,
|
||||
entityDetailRequestSequence: 0,
|
||||
stockPreviewCode: "",
|
||||
stockPreviewType: "stock",
|
||||
stockPreviewItem: null,
|
||||
stockPreviewPayload: null,
|
||||
stockPreviewChart: "daily",
|
||||
stockPreviewFallback: null,
|
||||
@@ -195,7 +197,6 @@ const elements = {
|
||||
globalSearchResults: document.querySelector("#globalSearchResults"),
|
||||
entityDetailDialog: document.querySelector("#entityDetailDialog"),
|
||||
entityDetailChart: document.querySelector("#entityDetailChart"),
|
||||
themeDetailChart: document.querySelector("#themeDetailChart"),
|
||||
settingsDialog: document.querySelector("#settingsDialog"),
|
||||
adminDialog: document.querySelector("#adminDialog"),
|
||||
priceChart: document.querySelector("#priceChart"),
|
||||
@@ -366,9 +367,6 @@ function redrawThemeSensitiveVisuals() {
|
||||
if (state.activeView === "sentimentCycleView" && state.sentimentHistory) {
|
||||
drawSentimentTrendChart(state.sentimentHistory.rows || []);
|
||||
}
|
||||
if (state.activeView === "themeLibraryView" && state.themeDetail?.series) {
|
||||
drawEntityDetailChart(state.themeDetail.series, elements.themeDetailChart);
|
||||
}
|
||||
if (state.activeView === "heavenView") {
|
||||
if (state.heavenPanel === "fortune" && state.heavenSetup?.field) {
|
||||
renderQiFieldCanvas(state.heavenSetup.field.balance || [], { intro: false });
|
||||
@@ -430,11 +428,12 @@ async function initialize() {
|
||||
syncThemeControl();
|
||||
refreshIcons();
|
||||
initializeApplicationShell();
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const requestedDate = searchParams.get("date");
|
||||
elements.tradeDate.value = /^\d{4}-\d{2}-\d{2}$/.test(requestedDate || "") && requestedDate <= todayString()
|
||||
? requestedDate
|
||||
: todayString();
|
||||
elements.tradeDate.value = todayString();
|
||||
const initialUrl = new URL(window.location.href);
|
||||
if (initialUrl.searchParams.has("date")) {
|
||||
initialUrl.searchParams.delete("date");
|
||||
history.replaceState(null, "", initialUrl);
|
||||
}
|
||||
elements.tradeDate.max = todayString();
|
||||
document.querySelector("#journalDate").value = elements.tradeDate.value;
|
||||
document.querySelector("#journalDate").max = todayString();
|
||||
@@ -581,7 +580,6 @@ function bindEvents() {
|
||||
state.heavenRequestSequence += 1;
|
||||
state.heavenManualData = null;
|
||||
document.querySelector("#qiObservationDate").value = elements.tradeDate.value;
|
||||
setDateInUrl(elements.tradeDate.value);
|
||||
loadDashboard();
|
||||
});
|
||||
document.querySelector("#prevDate").addEventListener("click", () => shiftDate(-1));
|
||||
@@ -673,6 +671,7 @@ function bindEvents() {
|
||||
if (window.innerWidth > 720) toggleMentorDirectory(false);
|
||||
if (!elements.stockPreview.hidden) closeStockPreview();
|
||||
updateSidebarControl();
|
||||
syncNavigationState(state.activeView);
|
||||
if (state.activeView === "dragonView") layoutDragonCards();
|
||||
});
|
||||
document.querySelectorAll("[data-open-view]").forEach((button) => {
|
||||
@@ -1273,14 +1272,6 @@ function renderSentimentHistory() {
|
||||
setText("sentimentNormalization", `${latest.normalization} · 当前展示 ${rows.length} 日`);
|
||||
const marker = document.querySelector("#sentimentCycleScoreMarker");
|
||||
marker.className = `sentiment-current-phase-badge ${sentimentPhaseClass(latest.phase)}`;
|
||||
document.querySelectorAll("[data-sentiment-stage]").forEach((item) => {
|
||||
const current = item.dataset.sentimentStage === latest.phase;
|
||||
item.classList.toggle("current", current);
|
||||
item.hidden = !current;
|
||||
const label = item.querySelector("strong");
|
||||
if (label) label.textContent = `${item.dataset.sentimentStage}${current ? "(当前)" : ""}`;
|
||||
});
|
||||
|
||||
document.querySelector("#sentimentComponentList").innerHTML = Object.values(latest.components || {}).map((item) => `
|
||||
<article class="sentiment-component-item">
|
||||
<div class="sentiment-component-main">
|
||||
@@ -2330,7 +2321,7 @@ function scheduleAuctionTransition(meta) {
|
||||
clearAuctionTimer();
|
||||
if (state.activeView !== "auctionView") return;
|
||||
let delay = 0;
|
||||
if (meta.phase === "selection" && !meta.available) {
|
||||
if (["selection", "finalized"].includes(meta.phase) && !meta.available) {
|
||||
delay = 10_000;
|
||||
} else if (meta.next_transition_at) {
|
||||
const transitionAt = new Date(meta.next_transition_at).getTime();
|
||||
@@ -2518,7 +2509,7 @@ function renderThemeDirectory() {
|
||||
return `
|
||||
<button type="button" class="theme-directory-item-v2 ${active ? "active" : ""}" data-theme-code="${escapeHtml(item.code)}" aria-pressed="${active}">
|
||||
<span class="theme-rank-v2">${index + 1}</span>
|
||||
<span class="theme-directory-copy-v2"><strong>${escapeHtml(item.name)}</strong><small>${number(item.member_count)} 只成分${item.hot_rank ? ` · 人气第 ${number(item.hot_rank)}` : ""}</small></span>
|
||||
<span class="theme-directory-copy-v2"><strong class="market-preview-trigger" data-market-preview-type="theme" data-market-preview-id="${escapeHtml(item.code)}" title="悬停预览题材行情">${escapeHtml(item.name)}</strong><small>${number(item.member_count)} 只成分${item.hot_rank ? ` · 人气第 ${number(item.hot_rank)}` : ""}</small></span>
|
||||
<b class="${changeClass(item.change)}">${item.has_quote ? `${signed(item.change)}%` : "--"}</b>
|
||||
</button>`;
|
||||
}).join("") || '<div class="empty-state">没有匹配的题材</div>';
|
||||
@@ -2567,7 +2558,6 @@ function renderThemeDetail() {
|
||||
<td class="number num ${changeClass(row.change)}">${row.has_quote ? signed(row.change) : ""}</td>
|
||||
<td class="number num">${row.has_quote ? formatNumber(row.price, 2) : ""}</td><td class="number num">${row.has_quote ? formatNumber(row.amount_billion, 2) : ""}</td></tr>`).join("");
|
||||
bindStockRows(body);
|
||||
requestAnimationFrame(() => drawEntityDetailChart(payload.series || [], elements.themeDetailChart));
|
||||
renderThemeDirectory();
|
||||
}
|
||||
|
||||
@@ -3748,15 +3738,24 @@ function curatedStrategySchool(strategy) {
|
||||
if (["行业轮动", "形态突破", "趋势追踪"].includes(category)) return "趋势";
|
||||
if (["短线竞价", "连板接力", "低吸反核"].includes(category)) return "短线";
|
||||
if (["动量反转"].includes(category)) return "动量";
|
||||
if (["元策略", "多因子"].includes(category)) return "量化";
|
||||
if (["业绩事件", "热度观察"].includes(category)) return "事件";
|
||||
if (["资金席位"].includes(category)) return "资金";
|
||||
if (/红利|价值|质量|成长|财务|现金流/.test(category)) return "基本面";
|
||||
if (/趋势|轮动|突破/.test(category)) return "趋势";
|
||||
if (/竞价|连板|龙头|反核|首阴|反包|打板/.test(category)) return "短线";
|
||||
if (/动量|反转/.test(category)) return "动量";
|
||||
if (/因子|量化|元策略/.test(category)) return "量化";
|
||||
if (/事件|热度|公告|业绩/.test(category)) return "事件";
|
||||
if (/席位|资金/.test(category)) return "资金";
|
||||
return "其他";
|
||||
}
|
||||
|
||||
function curatedSchoolIcon(school) {
|
||||
return { 基本面: "circle-dollar-sign", 趋势: "trending-up", 短线: "zap", 动量: "refresh-cw", 其他: "boxes" }[school] || "boxes";
|
||||
return {
|
||||
基本面: "circle-dollar-sign", 趋势: "trending-up", 短线: "zap",
|
||||
动量: "refresh-cw", 量化: "binary", 事件: "calendar-clock", 资金: "landmark", 其他: "boxes",
|
||||
}[school] || "boxes";
|
||||
}
|
||||
|
||||
function curatedStrategyRunState(strategy, result) {
|
||||
@@ -3774,7 +3773,7 @@ function renderCuratedStrategyLibrary() {
|
||||
if (!state.screenerSetup) return;
|
||||
const strategies = curatedStrategies();
|
||||
const categories = ["全部", ...new Set(strategies.map((item) => item.formula?.meta?.category || "其他"))];
|
||||
const schools = ["全部", "基本面", "趋势", "短线", "动量"];
|
||||
const schools = ["全部", "基本面", "趋势", "短线", "动量", "量化", "事件", "资金"];
|
||||
if (!categories.includes(state.curatedCategory)) state.curatedCategory = "全部";
|
||||
if (!schools.includes(state.curatedSchool)) state.curatedSchool = "全部";
|
||||
setText("curatedStrategyCount", `${strategies.length} 套`);
|
||||
@@ -7315,6 +7314,37 @@ function stockCodeFromTrigger(trigger) {
|
||||
return matched ? matched[1] : "";
|
||||
}
|
||||
|
||||
function marketPreviewTargetFromTrigger(trigger) {
|
||||
if (trigger?.classList?.contains("market-preview-trigger")) {
|
||||
const type = String(trigger.dataset.marketPreviewType || "").trim().toLowerCase();
|
||||
const id = String(trigger.dataset.marketPreviewId || "").trim().toUpperCase();
|
||||
if (type === "theme" && id) {
|
||||
const item = (state.themeLibrary?.items || []).find((row) => String(row.code) === id) || {};
|
||||
return {
|
||||
type,
|
||||
id,
|
||||
code: id,
|
||||
name: item.name || trigger.textContent?.trim() || "--",
|
||||
type_label: "题材",
|
||||
change: item.change,
|
||||
value: item.close,
|
||||
};
|
||||
}
|
||||
}
|
||||
const code = stockCodeFromTrigger(trigger);
|
||||
return code ? { type: "stock", id: code, code } : null;
|
||||
}
|
||||
|
||||
function previewTriggerFromEvent(event) {
|
||||
return event.target.closest?.(".stock-preview-trigger, .market-preview-trigger");
|
||||
}
|
||||
|
||||
function showMarketPreview(target, trigger) {
|
||||
if (!target) return;
|
||||
if (target.type === "stock") showStockPreview(target.id, trigger);
|
||||
else showEntityPreview(target, trigger);
|
||||
}
|
||||
|
||||
function findStockFallback(code) {
|
||||
const dashboardRows = [
|
||||
...(state.dashboard?.limits || []),
|
||||
@@ -7346,19 +7376,19 @@ function supportsStockPreviewHover() {
|
||||
|
||||
function handleStockPreviewPointerOver(event) {
|
||||
if (!supportsStockPreviewHover()) return;
|
||||
const trigger = event.target.closest?.(".stock-preview-trigger");
|
||||
if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger")) return;
|
||||
const code = stockCodeFromTrigger(trigger);
|
||||
if (!code) return;
|
||||
const trigger = previewTriggerFromEvent(event);
|
||||
if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger, .market-preview-trigger")) return;
|
||||
const target = marketPreviewTargetFromTrigger(trigger);
|
||||
if (!target) return;
|
||||
cancelStockPreviewClose();
|
||||
clearTimeout(stockPreviewOpenTimer);
|
||||
stockPreviewOpenTimer = setTimeout(() => showStockPreview(code, trigger), STOCK_PREVIEW_DELAY);
|
||||
stockPreviewOpenTimer = setTimeout(() => showMarketPreview(target, trigger), STOCK_PREVIEW_DELAY);
|
||||
}
|
||||
|
||||
function handleStockPreviewPointerOut(event) {
|
||||
if (!supportsStockPreviewHover()) return;
|
||||
const trigger = event.target.closest?.(".stock-preview-trigger");
|
||||
if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger")) return;
|
||||
const trigger = previewTriggerFromEvent(event);
|
||||
if (!trigger || trigger === event.relatedTarget?.closest?.(".stock-preview-trigger, .market-preview-trigger")) return;
|
||||
clearTimeout(stockPreviewOpenTimer);
|
||||
if (event.relatedTarget instanceof Node && elements.stockPreview.contains(event.relatedTarget)) return;
|
||||
scheduleStockPreviewClose();
|
||||
@@ -7424,6 +7454,8 @@ async function showStockPreview(code, trigger) {
|
||||
if (!/^\d{6}$/.test(String(code))) return;
|
||||
stockPreviewAnchor = trigger;
|
||||
state.stockPreviewCode = String(code);
|
||||
state.stockPreviewType = "stock";
|
||||
state.stockPreviewItem = null;
|
||||
state.stockPreviewFallback = findStockFallback(code);
|
||||
state.stockPreviewPayload = null;
|
||||
state.stockPreviewChart = "daily";
|
||||
@@ -7461,8 +7493,80 @@ async function showStockPreview(code, trigger) {
|
||||
}
|
||||
}
|
||||
|
||||
async function showEntityPreview(item, trigger) {
|
||||
const type = String(item?.type || "").trim().toLowerCase();
|
||||
const id = String(item?.id || item?.code || "").trim().toUpperCase();
|
||||
if (type !== "theme" || !id) return;
|
||||
clearTimeout(stockPreviewOpenTimer);
|
||||
cancelStockPreviewClose();
|
||||
stockPreviewAnchor = trigger;
|
||||
state.stockPreviewCode = id;
|
||||
state.stockPreviewType = type;
|
||||
state.stockPreviewItem = { ...item, id, code: item.code || id, type, type_label: item.type_label || "题材" };
|
||||
state.stockPreviewFallback = {
|
||||
code: item.code || id,
|
||||
name: item.name || "--",
|
||||
sector: item.type_label || "题材",
|
||||
price: item.value,
|
||||
change: item.change,
|
||||
};
|
||||
state.stockPreviewPayload = null;
|
||||
state.stockPreviewChart = "daily";
|
||||
renderStockPreviewLoading();
|
||||
elements.stockPreview.hidden = false;
|
||||
const mobile = window.innerWidth <= 720;
|
||||
elements.stockPreviewBackdrop.hidden = !mobile;
|
||||
document.body.classList.toggle("stock-preview-open", mobile);
|
||||
requestAnimationFrame(repositionStockPreview);
|
||||
|
||||
const cacheKey = `${type}:${id}:latest`;
|
||||
const cached = stockPreviewCache.get(cacheKey);
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
renderStockPreview(cached.payload);
|
||||
return;
|
||||
}
|
||||
if (cached) stockPreviewCache.delete(cacheKey);
|
||||
stockPreviewAbortController?.abort();
|
||||
stockPreviewAbortController = new AbortController();
|
||||
try {
|
||||
const params = new URLSearchParams({ type, id, trade_date: todayString() });
|
||||
const detail = await apiRequest(
|
||||
`/api/search/detail?${params}`,
|
||||
"GET",
|
||||
null,
|
||||
{ signal: stockPreviewAbortController.signal },
|
||||
);
|
||||
if (state.stockPreviewType !== type || state.stockPreviewCode !== id || elements.stockPreview.hidden) return;
|
||||
const entity = detail.entity || {};
|
||||
const payload = {
|
||||
stock: {
|
||||
code: entity.code || id,
|
||||
name: entity.name || item.name || "--",
|
||||
industry: entity.type_label || item.type_label || "题材",
|
||||
price: entity.value,
|
||||
change: entity.change,
|
||||
},
|
||||
prices: detail.series || [],
|
||||
intraday: [],
|
||||
meta: {
|
||||
trade_date: detail.meta?.trade_date || "",
|
||||
realtime: Boolean(detail.meta?.realtime),
|
||||
intraday_status: "idle",
|
||||
intraday_notice: "",
|
||||
},
|
||||
};
|
||||
stockPreviewCache.set(cacheKey, { payload, expiresAt: Date.now() + STOCK_PREVIEW_CACHE_MS });
|
||||
while (stockPreviewCache.size > 48) stockPreviewCache.delete(stockPreviewCache.keys().next().value);
|
||||
renderStockPreview(payload);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError" || state.stockPreviewType !== type || state.stockPreviewCode !== id) return;
|
||||
renderStockPreviewError(error.message || "题材行情预览加载失败");
|
||||
}
|
||||
}
|
||||
|
||||
function renderStockPreviewLoading() {
|
||||
const fallback = state.stockPreviewFallback || {};
|
||||
selectStockPreviewChart("daily");
|
||||
setText("stockPreviewCode", state.stockPreviewCode || "--");
|
||||
setText("stockPreviewName", fallback.name || "正在加载");
|
||||
setText("stockPreviewSector", fallback.sector || "--");
|
||||
@@ -7510,6 +7614,16 @@ function selectStockPreviewChart(chart) {
|
||||
const payload = state.stockPreviewPayload;
|
||||
if (!payload) return;
|
||||
if (state.stockPreviewChart === "intraday") {
|
||||
if (state.stockPreviewType !== "stock" && payload.meta?.intraday_status === "idle") {
|
||||
payload.meta.intraday_status = "loading";
|
||||
setText("stockPreviewDate", "正在加载分时");
|
||||
setText("stockPreviewSource", "正在读取最新分时");
|
||||
setText("stockPreviewSummary", "等待分时行情数据");
|
||||
clearStockPreviewChart("");
|
||||
loadEntityPreviewIntraday();
|
||||
return;
|
||||
}
|
||||
if (state.stockPreviewType !== "stock" && payload.meta?.intraday_status === "loading") return;
|
||||
setText("stockPreviewDate", payload.meta?.intraday_trade_date || payload.meta?.trade_date || "最新行情");
|
||||
setText(
|
||||
"stockPreviewSource",
|
||||
@@ -7538,6 +7652,36 @@ function selectStockPreviewChart(chart) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEntityPreviewIntraday() {
|
||||
const type = state.stockPreviewType;
|
||||
const id = state.stockPreviewCode;
|
||||
const payload = state.stockPreviewPayload;
|
||||
if (type === "stock" || !id || !payload) return;
|
||||
stockPreviewAbortController?.abort();
|
||||
stockPreviewAbortController = new AbortController();
|
||||
try {
|
||||
const params = new URLSearchParams({ type, id });
|
||||
const intraday = await apiRequest(
|
||||
`/api/chart/intraday?${params}`,
|
||||
"GET",
|
||||
null,
|
||||
{ signal: stockPreviewAbortController.signal },
|
||||
);
|
||||
if (state.stockPreviewType !== type || state.stockPreviewCode !== id || elements.stockPreview.hidden) return;
|
||||
payload.intraday = intraday.points || [];
|
||||
payload.meta.intraday_status = payload.intraday.length ? "available" : "empty";
|
||||
payload.meta.intraday_trade_date = intraday.meta?.trade_date || "";
|
||||
payload.meta.intraday_previous_close = intraday.meta?.previous_close || 0;
|
||||
payload.meta.intraday_notice = payload.intraday.length ? "" : "该题材暂无可用分时数据。";
|
||||
if (state.stockPreviewChart === "intraday") selectStockPreviewChart("intraday");
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError" || state.stockPreviewType !== type || state.stockPreviewCode !== id) return;
|
||||
payload.meta.intraday_status = "unavailable";
|
||||
payload.meta.intraday_notice = error.message || "题材分时行情暂不可用。";
|
||||
if (state.stockPreviewChart === "intraday") selectStockPreviewChart("intraday");
|
||||
}
|
||||
}
|
||||
|
||||
function closeStockPreview() {
|
||||
clearTimeout(stockPreviewOpenTimer);
|
||||
clearTimeout(stockPreviewCloseTimer);
|
||||
@@ -7548,14 +7692,19 @@ function closeStockPreview() {
|
||||
document.body.classList.remove("stock-preview-open");
|
||||
state.stockPreviewPayload = null;
|
||||
state.stockPreviewCode = "";
|
||||
state.stockPreviewType = "stock";
|
||||
state.stockPreviewItem = null;
|
||||
}
|
||||
|
||||
function openStockDetailFromPreview() {
|
||||
const code = state.stockPreviewCode;
|
||||
const fallback = state.stockPreviewFallback;
|
||||
const type = state.stockPreviewType;
|
||||
const item = state.stockPreviewItem;
|
||||
if (!code) return;
|
||||
closeStockPreview();
|
||||
openStock(code, fallback);
|
||||
if (type === "stock") openStock(code, fallback);
|
||||
else if (item) openEntityDetail(item);
|
||||
}
|
||||
|
||||
function repositionStockPreview() {
|
||||
@@ -8484,16 +8633,9 @@ function shiftDate(delta) {
|
||||
elements.tradeDate.value = next;
|
||||
state.heavenManualData = null;
|
||||
document.querySelector("#qiObservationDate").value = next;
|
||||
setDateInUrl(next);
|
||||
loadDashboard();
|
||||
}
|
||||
|
||||
function setDateInUrl(value) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("date", value);
|
||||
history.replaceState(null, "", url);
|
||||
}
|
||||
|
||||
function updateDateButtons() {
|
||||
document.querySelector("#nextDate").disabled = elements.tradeDate.value >= todayString();
|
||||
}
|
||||
@@ -9198,7 +9340,10 @@ function syncNavigationState(viewId) {
|
||||
button.dataset.view === viewId
|
||||
|| (viewId === "screenerTrackingView" && button.dataset.view === "screenerView"),
|
||||
);
|
||||
button.classList.remove("mobile-active");
|
||||
button.classList.toggle(
|
||||
"mobile-active",
|
||||
window.innerWidth <= 720 && marketView && button.dataset.view === "limitPool" && viewId !== "limitPool",
|
||||
);
|
||||
});
|
||||
const selector = document.querySelector("#mobileMarketSelector");
|
||||
const select = document.querySelector("#mobileMarketViewSelect");
|
||||
|
||||
@@ -888,7 +888,7 @@ tbody tr.clickable{cursor:pointer}
|
||||
|
||||
#themeLibraryView.active-view{overflow:hidden}
|
||||
#themeLibraryView .theme-library-workspace-v2{height:100%;grid-template-rows:minmax(0,1fr);align-items:stretch;overflow:hidden}
|
||||
#themeLibraryView .theme-detail-stack-v2{grid-template-rows:minmax(0,1.35fr) minmax(0,.85fr)}
|
||||
#themeLibraryView .theme-detail-stack-v2{grid-template-rows:auto minmax(0,1fr)}
|
||||
#themeLibraryView .theme-directory-card-v2,
|
||||
#themeLibraryView .theme-detail-column-v2,
|
||||
#themeLibraryView .theme-detail-stack-v2{height:100%;min-height:0;overflow:hidden}
|
||||
@@ -926,7 +926,9 @@ tbody tr.clickable{cursor:pointer}
|
||||
:root body:is(
|
||||
[data-active-view="sentimentCycleView"],
|
||||
[data-active-view="rotationView"],
|
||||
[data-active-view="screenerView"]
|
||||
[data-active-view="screenerView"],
|
||||
[data-active-view="ladderView"],
|
||||
[data-active-view="reviewWorkspaceView"]
|
||||
) .app-main{
|
||||
height:var(--workspace-height);
|
||||
min-height:0;
|
||||
@@ -937,7 +939,9 @@ tbody tr.clickable{cursor:pointer}
|
||||
|
||||
:root #sentimentCycleView.active-view,
|
||||
:root #rotationView.active-view,
|
||||
:root #screenerView.active-view{
|
||||
:root #screenerView.active-view,
|
||||
:root #ladderView.active-view,
|
||||
:root #reviewWorkspaceView.active-view{
|
||||
height:auto;
|
||||
min-height:0;
|
||||
display:block;
|
||||
|
||||
+11
-28
@@ -96,8 +96,8 @@
|
||||
</div>
|
||||
<div class="nav-group market-nav-group">
|
||||
<div class="nav-group-label">市场复盘</div>
|
||||
<button class="module-tab market-sub-tab" type="button" data-view="sentimentCycleView" title="情绪周期"><i data-lucide="activity"></i><span>情绪周期</span></button>
|
||||
<button class="module-tab active mobile-primary-tab" type="button" data-view="limitPool" title="涨停池"><i data-lucide="flame"></i><span class="nav-label-desktop">涨停池</span><span class="nav-label-mobile">行情</span></button>
|
||||
<button class="module-tab market-sub-tab active" type="button" data-view="sentimentCycleView" title="情绪周期"><i data-lucide="activity"></i><span>情绪周期</span></button>
|
||||
<button class="module-tab mobile-primary-tab" type="button" data-view="limitPool" title="涨停池"><i data-lucide="flame"></i><span class="nav-label-desktop">涨停池</span><span class="nav-label-mobile">行情</span></button>
|
||||
<button class="module-tab market-sub-tab" type="button" data-view="brokenView" title="炸板池"><i data-lucide="bomb"></i><span>炸板池</span></button>
|
||||
<button class="module-tab market-sub-tab" type="button" data-view="downView" title="跌停板"><i data-lucide="trending-down"></i><span>跌停板</span></button>
|
||||
<button class="module-tab market-sub-tab" type="button" data-view="yesterdayView" title="昨日涨停"><i data-lucide="history"></i><span>昨日涨停</span></button>
|
||||
@@ -182,7 +182,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="limitPool" class="workspace-view page active-view redesigned-pool-view">
|
||||
<section id="limitPool" class="workspace-view page redesigned-pool-view">
|
||||
<div class="section-toolbar lad-head redesigned-page-head pool-page-head">
|
||||
<div class="section-title-group">
|
||||
<h2>涨停池</h2>
|
||||
@@ -419,7 +419,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="sentimentCycleView" class="workspace-view page redesigned-sentiment-view">
|
||||
<section id="sentimentCycleView" class="workspace-view page active-view redesigned-sentiment-view">
|
||||
<div class="section-toolbar lad-head sentiment-cycle-toolbar redesigned-page-head">
|
||||
<div class="section-title-group">
|
||||
<h2>情绪周期</h2>
|
||||
@@ -445,18 +445,6 @@
|
||||
<div id="sentimentChartTooltip" class="sentiment-chart-tooltip" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="sentiment-stage-guide card redesigned-card" aria-labelledby="sentimentStageGuideTitle">
|
||||
<div class="workspace-heading card-h redesigned-card-head"><h3 id="sentimentStageGuideTitle">判定口径</h3><span>温度 + 结构共同判定</span></div>
|
||||
<div class="sentiment-stage-guide-head" aria-hidden="true"><span>阶段</span><span>典型特征</span><span>温度区间</span><span>策略取向</span></div>
|
||||
<div class="sentiment-stage-guide-grid">
|
||||
<article data-sentiment-stage="冰点"><strong>冰点</strong><span>涨停稀少、跌停成堆、高度显著压缩</span><span class="stage-range">低于 25</span><small>抗跌先手,允许无结果</small></article>
|
||||
<article data-sentiment-stage="修复"><strong>修复</strong><span>风险收敛、温度从低位有效回升</span><span class="stage-range">25+ 且回升</span><small>修复先锋,小仓试错</small></article>
|
||||
<article data-sentiment-stage="发酵"><strong>发酵</strong><span>主线清晰、梯队成型、连续转强</span><span class="stage-range">45+ 且连续确认</span><small>主线跟随</small></article>
|
||||
<article data-sentiment-stage="高潮"><strong>高潮</strong><span>温度、赚钱效应与涨停生态共振</span><span class="stage-range">80+ 且生态达标</span><small>核心去后排</small></article>
|
||||
<article data-sentiment-stage="分化"><strong>分化</strong><span>高低切换、炸板增多、主线内部分歧</span><span class="stage-range">45+ 且结构转弱</span><small>承接回流</small></article>
|
||||
<article data-sentiment-stage="退潮"><strong>退潮</strong><span>温度或系统健康度继续走弱</span><span class="stage-range">低于 45 且走弱</span><small>防守观察</small></article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<aside class="sentiment-analysis-rail">
|
||||
<section class="sentiment-cycle-summary card redesigned-card" aria-label="最新情绪状态">
|
||||
@@ -861,7 +849,7 @@
|
||||
<div class="curated-workspace">
|
||||
<aside class="curated-library-pane" aria-label="精选策略库">
|
||||
<div class="curated-library-heading">
|
||||
<div><span>策略库</span><h3>盘后自动候选池</h3></div><strong id="curatedStrategyCount">10 套</strong>
|
||||
<div><span>策略库</span><h3>盘后自动候选池</h3></div><strong id="curatedStrategyCount">29 套</strong>
|
||||
</div>
|
||||
<div class="curated-library-controls">
|
||||
<label class="curated-search"><i data-lucide="search"></i><span class="visually-hidden">搜索策略</span><input id="curatedStrategySearch" type="search" placeholder="搜索策略" autocomplete="off"></label>
|
||||
@@ -1216,7 +1204,7 @@
|
||||
<section id="themeLibraryView" class="workspace-view page redesigned-theme-view">
|
||||
<header class="theme-page-head-v2 lad-head">
|
||||
<div class="theme-title-v2">
|
||||
<div><h2>题材库</h2><span>题材走势与成分股</span></div>
|
||||
<div><h2>题材库</h2><span>题材行情与成分股</span></div>
|
||||
<span id="themeDateLabel" class="theme-date-v2">--</span>
|
||||
</div>
|
||||
<div class="theme-head-actions-v2">
|
||||
@@ -1242,7 +1230,7 @@
|
||||
</aside>
|
||||
|
||||
<section class="theme-detail-column-v2" aria-live="polite">
|
||||
<div id="themeDetailEmpty" class="empty-state theme-detail-empty-v2">选择题材查看走势与成分股</div>
|
||||
<div id="themeDetailEmpty" class="empty-state theme-detail-empty-v2">选择题材查看行情与成分股</div>
|
||||
<div id="themeDetailContent" class="theme-detail-stack-v2" hidden>
|
||||
<section class="theme-market-card-v2 card">
|
||||
<header class="theme-detail-heading-v2">
|
||||
@@ -1253,11 +1241,6 @@
|
||||
<div class="theme-change-v2"><span>当日涨幅</span><strong id="themeDetailChange">--</strong></div>
|
||||
</header>
|
||||
<div id="themeDetailMetrics" class="theme-detail-metrics-v2"></div>
|
||||
<div class="theme-chart-heading-v2">
|
||||
<div><h4>题材走势</h4><span>日 K · 最近 90 个交易日</span></div>
|
||||
<div class="theme-chart-legend-v2" aria-label="日K图例"><span><i class="up"></i>上涨</span><span><i class="down"></i>下跌</span><span><i class="volume"></i>成交量</span></div>
|
||||
</div>
|
||||
<div class="theme-chart-shell-v2"><canvas id="themeDetailChart" aria-label="题材日K图"></canvas></div>
|
||||
</section>
|
||||
|
||||
<section class="theme-members-card-v2 card">
|
||||
@@ -1543,7 +1526,7 @@
|
||||
</dialog>
|
||||
|
||||
<div id="stockPreviewBackdrop" class="stock-preview-backdrop" hidden></div>
|
||||
<aside id="stockPreview" class="stock-preview" aria-label="个股行情预览" aria-live="polite" hidden>
|
||||
<aside id="stockPreview" class="stock-preview" aria-label="行情预览" aria-live="polite" hidden>
|
||||
<header class="stock-preview-header">
|
||||
<div class="stock-preview-identity">
|
||||
<span id="stockPreviewCode">--</span>
|
||||
@@ -1557,12 +1540,12 @@
|
||||
<button id="closeStockPreview" class="icon-button stock-preview-close" type="button" title="关闭预览" aria-label="关闭预览"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
<div class="stock-preview-tabs" role="tablist" aria-label="行情图表">
|
||||
<button class="stock-preview-tab active" type="button" role="tab" aria-selected="true" data-preview-chart="intraday">分时</button>
|
||||
<button class="stock-preview-tab" type="button" role="tab" aria-selected="false" data-preview-chart="daily">日K</button>
|
||||
<button class="stock-preview-tab" type="button" role="tab" aria-selected="false" data-preview-chart="intraday">分时</button>
|
||||
<button class="stock-preview-tab active" type="button" role="tab" aria-selected="true" data-preview-chart="daily">日K</button>
|
||||
<span id="stockPreviewDate">--</span>
|
||||
</div>
|
||||
<div class="stock-preview-chart-shell">
|
||||
<canvas id="stockPreviewChart" width="488" height="232" aria-label="个股行情图" aria-describedby="stockPreviewSummary"></canvas>
|
||||
<canvas id="stockPreviewChart" width="488" height="232" aria-label="行情图" aria-describedby="stockPreviewSummary"></canvas>
|
||||
<div id="stockPreviewLoading" class="stock-preview-loading">
|
||||
<span class="spinner" aria-hidden="true"></span>
|
||||
<span>正在读取行情</span>
|
||||
|
||||
+5
-31
@@ -3501,8 +3501,6 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-head-actions-v2,
|
||||
.theme-title-v2 > div,
|
||||
.theme-detail-name-line-v2,
|
||||
.theme-chart-heading-v2,
|
||||
.theme-chart-legend-v2,
|
||||
.theme-members-heading-v2,
|
||||
.theme-members-heading-v2 > div {
|
||||
display: flex;
|
||||
@@ -3627,8 +3625,7 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
}
|
||||
|
||||
.theme-card-head-v2 h3,
|
||||
.theme-members-heading-v2 h3,
|
||||
.theme-chart-heading-v2 h4 { margin: 0; color: var(--r2-ink); }
|
||||
.theme-members-heading-v2 h3 { margin: 0; color: var(--r2-ink); }
|
||||
.theme-card-head-v2 h3 { font-size: 14px; font-weight: 700; }
|
||||
.theme-card-head-v2 span { display: block; margin-top: 2px; color: var(--r2-faint); font-size: 10.5px; }
|
||||
.theme-card-head-v2 > strong {
|
||||
@@ -3717,7 +3714,7 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-detail-stack-v2:not([hidden]) {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(348px, 1.35fr) minmax(190px, .85fr);
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@@ -3773,25 +3770,6 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-detail-metrics-v2 strong.up { color: var(--r2-up); }
|
||||
.theme-detail-metrics-v2 strong.down { color: var(--r2-down); }
|
||||
|
||||
.theme-chart-heading-v2 {
|
||||
min-height: 39px;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 6px 14px 4px;
|
||||
}
|
||||
|
||||
.theme-chart-heading-v2 h4 { display: inline; font-size: 12px; font-weight: 700; }
|
||||
.theme-chart-heading-v2 > div:first-child > span { margin-left: 6px; color: var(--r2-faint); font-size: 10px; }
|
||||
.theme-chart-legend-v2 { gap: 12px; color: var(--r2-faint); font-size: 9.5px; }
|
||||
.theme-chart-legend-v2 span { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.theme-chart-legend-v2 i { width: 8px; height: 8px; display: inline-block; border-radius: 2px; }
|
||||
.theme-chart-legend-v2 i.up { background: var(--r2-up); }
|
||||
.theme-chart-legend-v2 i.down { background: var(--r2-down); }
|
||||
.theme-chart-legend-v2 i.volume { height: 5px; background: #a8b2c1; }
|
||||
|
||||
.theme-chart-shell-v2 { min-height: 220px; flex: 1 1 auto; padding: 0 10px 8px; }
|
||||
.theme-chart-shell-v2 canvas { width: 100%; height: 100%; display: block; }
|
||||
|
||||
.theme-members-heading-v2 {
|
||||
min-height: 50px;
|
||||
justify-content: space-between;
|
||||
@@ -3858,7 +3836,7 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-directory-card-v2 { max-height: 330px; }
|
||||
.theme-directory-v2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.theme-directory-item-v2:nth-child(odd) { border-right: 1px solid var(--r2-line-soft); }
|
||||
.theme-detail-stack-v2:not([hidden]) { grid-template-rows: 430px 360px; }
|
||||
.theme-detail-stack-v2:not([hidden]) { grid-template-rows: auto 420px; }
|
||||
}
|
||||
|
||||
@media (min-width: 721px) and (max-height: 900px) {
|
||||
@@ -3869,11 +3847,9 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-card-head-v2 { min-height: 50px; }
|
||||
.theme-directory-labels-v2 { min-height: 25px; }
|
||||
.theme-directory-item-v2 { min-height: 52px; }
|
||||
.theme-detail-stack-v2:not([hidden]) { grid-template-rows: minmax(330px, 1.35fr) minmax(180px, .85fr); gap: 9px; }
|
||||
.theme-detail-stack-v2:not([hidden]) { grid-template-rows: auto minmax(260px, 1fr); gap: 9px; }
|
||||
.theme-detail-heading-v2 { min-height: 58px; }
|
||||
.theme-detail-metrics-v2 { min-height: 47px; }
|
||||
.theme-chart-heading-v2 { min-height: 34px; }
|
||||
.theme-chart-shell-v2 { min-height: 185px; }
|
||||
.theme-members-heading-v2 { min-height: 44px; }
|
||||
.theme-members-table-v2 tbody td { height: 35px; padding-top: 5px; padding-bottom: 5px; }
|
||||
}
|
||||
@@ -3892,15 +3868,13 @@ body.sidebar-collapsed .status-bar { left: 64px; }
|
||||
.theme-library-workspace-v2 { grid-template-columns: minmax(0, 1fr); }
|
||||
.theme-directory-card-v2 { max-height: 360px; }
|
||||
.theme-detail-stack-v2:not([hidden]) { display: flex; flex-direction: column; gap: 10px; }
|
||||
.theme-market-card-v2 { min-height: 430px; }
|
||||
.theme-market-card-v2 { min-height: 0; }
|
||||
.theme-detail-heading-v2 { align-items: flex-end; }
|
||||
.theme-detail-name-line-v2 { align-items: flex-start; flex-direction: column; gap: 2px; }
|
||||
.theme-detail-metrics-v2 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.theme-detail-metrics-v2 > div { border-bottom: 1px solid var(--r2-line-soft); }
|
||||
.theme-detail-metrics-v2 > div:nth-child(3) { border-right: 0; }
|
||||
.theme-detail-metrics-v2 > div:nth-child(n + 4) { border-bottom: 0; }
|
||||
.theme-chart-heading-v2 { align-items: flex-start; flex-direction: column; gap: 4px; }
|
||||
.theme-chart-shell-v2 { min-height: 245px; }
|
||||
.theme-members-card-v2 { min-height: 420px; }
|
||||
.theme-members-heading-v2 > div { align-items: flex-start; flex-direction: column; gap: 2px; }
|
||||
}
|
||||
|
||||
+6
-4
@@ -5688,7 +5688,8 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
|
||||
}
|
||||
|
||||
/* Phase 2 stock preview */
|
||||
.stock-preview-trigger {
|
||||
.stock-preview-trigger,
|
||||
.market-preview-trigger {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
@@ -5697,7 +5698,8 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
|
||||
}
|
||||
|
||||
.stock-preview-trigger:hover,
|
||||
.stock-preview-trigger:focus-visible {
|
||||
.stock-preview-trigger:focus-visible,
|
||||
.market-preview-trigger:hover {
|
||||
color: var(--action);
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
@@ -5846,7 +5848,7 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
padding: 6px 10px 2px;
|
||||
background: var(--surface);
|
||||
background: var(--chart-background);
|
||||
}
|
||||
|
||||
.stock-preview-chart-shell canvas {
|
||||
@@ -5862,7 +5864,7 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
background: var(--chart-background);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
+18
-2
@@ -528,7 +528,7 @@
|
||||
}
|
||||
|
||||
/* Theme library, popularity and dragon-tiger. */
|
||||
:root[data-theme="dark"] :is(.theme-summary-v2, .theme-directory-labels-v2, .theme-detail-metrics-v2, .theme-chart-heading-v2) {
|
||||
:root[data-theme="dark"] :is(.theme-summary-v2, .theme-directory-labels-v2, .theme-detail-metrics-v2) {
|
||||
border-color: var(--line-soft);
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
@@ -659,7 +659,7 @@
|
||||
color: var(--heaven-ink);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] :is(.price-chart, .stock-preview-chart, .entity-detail-chart, .theme-chart-shell-v2) {
|
||||
:root[data-theme="dark"] :is(.price-chart, .stock-preview-chart, .entity-detail-chart) {
|
||||
border-color: var(--border);
|
||||
background: var(--chart-background);
|
||||
}
|
||||
@@ -1236,6 +1236,22 @@
|
||||
background: var(--action-soft);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #mentorView :is(
|
||||
.mentor-message-content,
|
||||
.mentor-answer-heading,
|
||||
.loading-message p
|
||||
) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #mentorView .mentor-answer-quote {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #mentorView :is(.mentor-message-label, .mentor-message small) {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #reviewWorkspaceView {
|
||||
--review-blue: var(--action);
|
||||
--review-blue-dark: var(--action-hover);
|
||||
|
||||
Reference in New Issue
Block a user