diff --git a/frontend/m/css/features.css b/frontend/m/css/features.css index 9c979d0..921cd59 100644 --- a/frontend/m/css/features.css +++ b/frontend/m/css/features.css @@ -2172,17 +2172,19 @@ padding: 12px 12px 4px; display: flex; flex-direction: column; - gap: 4px; + gap: 0; } .m-chat-msg { display: flex; max-width: 82%; - margin-top: 8px; + margin-top: 12px; word-break: break-word; white-space: pre-wrap; } .m-chat-msg:first-child { margin-top: 12px; } +.m-chat-msg--user + .m-chat-msg--user, +.m-chat-msg--ai + .m-chat-msg--ai { margin-top: 4px; } .m-chat-msg--user { align-self: flex-end; } .m-chat-msg--ai { align-self: flex-start; } @@ -2304,10 +2306,11 @@ 30% { transform: translateY(-3px); opacity: 1; } } -/* 引导问题 / 继续追问胶囊 */ +/* 引导问题胶囊(空态时渲染在消息区内,横滑) */ .m-chat-presets { flex: 0 0 auto; - padding: 4px 12px 0; + width: 100%; + margin-top: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; @@ -2630,17 +2633,30 @@ color: var(--text-primary); } .m-screener-drawer-row:active { background: var(--surface-hover); } +.m-screener-drawer-main { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} .m-screener-drawer-name { font-size: 14px; font-weight: 600; - flex: 1 1 auto; - display: inline-flex; + display: flex; align-items: center; gap: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.m-screener-drawer-sub { + font-size: 11.5px; + color: var(--text-tertiary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} .m-strategy-status { font-size: 12px; color: var(--text-primary); } .m-strategy-status--warn { color: var(--warning); } .m-strategy-status--muted { color: var(--text-secondary); } diff --git a/frontend/m/js/pages.js b/frontend/m/js/pages.js index 102b380..cc1d53e 100644 --- a/frontend/m/js/pages.js +++ b/frontend/m/js/pages.js @@ -1620,16 +1620,8 @@ history: "入选历史" }; - function screenerViewKey(payload, view) { - if (!payload) return []; - if (view === "active") return payload.active_signals || []; - if (view === "history") return payload.candidate_history || []; - const latest = payload.latest_results || {}; - const strategyLibrary = screenerCurrentLibrary(payload); - const mode = strategyLibrary || "smart"; - const run = latest[mode] || latest.smart || latest.curated || null; - if (!run) return []; - return run.candidates || []; + function screenerCurrentStrategy(payload) { + return findScreenerStrategy(payload, state.screener.strategyId); } function screenerCurrentLibrary(payload) { @@ -1639,13 +1631,71 @@ return (s.formula && s.formula.meta && s.formula.meta.library) || "smart"; } + function screenerLibraryMode(library) { + if (library === "curated") return "curated"; + if (library === "quant" || library === "custom") return "quant"; + return "smart"; + } + + // 按当前策略名在 recent_results / latest_results 中匹配最新一次运行(对齐电脑端 activeScreenerResultEntry) + function screenerRunForCurrentStrategy(payload) { + if (!payload) return null; + const strategy = screenerCurrentStrategy(payload); + if (!strategy || !strategy.name) return null; + const name = strategy.name; + const recent = (payload.recent_results || []).slice().reverse(); + for (let i = 0; i < recent.length; i += 1) { + const run = recent[i]; + if (run && run.meta && run.meta.strategy_name === name) return run; + } + const latest = (payload.latest_results || {})[screenerLibraryMode(screenerCurrentLibrary(payload))]; + if (latest && latest.meta && latest.meta.strategy_name === name) return latest; + return null; + } + + // 持续有效 / 入选历史:对齐电脑端 selectedScreenerArchiveRows,按当前策略过滤 active_signals/candidate_history 组装行 + function screenerArchiveRows(payload, view) { + if (!payload) return []; + const source = view === "active" ? (payload.active_signals || []) : (payload.candidate_history || []); + const strategy = screenerCurrentStrategy(payload); + const strategyName = strategy ? strategy.name : ""; + const mode = screenerLibraryMode(screenerCurrentLibrary(payload)); + return source.filter(function (row) { + if ((row.mode || "smart") !== mode) return false; + return (row.hits || []).some(function (hit) { return hit.strategy_name === strategyName; }); + }).map(function (row) { + let hits = (row.hits || []).filter(function (hit) { return hit.strategy_name === strategyName; }); + if (view === "active") hits = hits.filter(function (hit) { return hit.active; }); + const latestHit = hits.slice().sort(function (a, b) { + return String(b.selection_date || "").localeCompare(String(a.selection_date || "")); + })[0] || {}; + return { + code: row.code, + name: row.name, + sector: row.sector, + pct_chg: row.pct_chg, + return_5d: row.return_5d, + score_display: latestHit.score_display != null ? latestHit.score_display : row.score_display, + selection_date: displayCompactDate(latestHit.selection_date || row.selection_date || ""), + status: view === "active" ? "持续有效" : "已入选" + }; + }); + } + + function screenerViewKey(payload, view) { + if (!payload) return []; + if (view === "active") return screenerArchiveRows(payload, "active"); + if (view === "history") return screenerArchiveRows(payload, "history"); + const run = screenerRunForCurrentStrategy(payload); + if (!run) return []; + return run.candidates || []; + } + function screenerCurrentRun(payload) { if (!payload) return null; if (state.screener.view === "active") return null; if (state.screener.view === "history") return null; - const latest = payload.latest_results || {}; - const mode = screenerCurrentLibrary(payload); - return latest[mode] || latest.smart || latest.curated || null; + return screenerRunForCurrentStrategy(payload); } function screenerCurrentRunId() { @@ -1810,6 +1860,16 @@ return "暂无信号"; } + function screenerEmptyText(payload) { + if (state.screener.view === "active") return "该策略暂无持续有效信号"; + if (state.screener.view === "history") return "该策略暂无入选历史"; + const status = screenerStrategyStatusText(payload); + if (status === "数据不足") return "该策略数据不足,暂无法出候选"; + if (status === "等待盘后") return "该策略等待盘后更新"; + if (status === "选择策略") return "请先选择一个策略查看结果"; + return "该策略当日暂无信号"; + } + function formatScreenerUpdatedAt(value) { if (!value) return "盘后已更新"; const text = String(value); @@ -1829,8 +1889,9 @@ if (!rows.length) { wrap.innerHTML = '
当前策略当日暂无信号
' + - '可点上方策略行切换其他策略' + + '' + escapeHtml(screenerEmptyText(payload)) + '
' + + '可切换其他策略查看结果' + + '' + '无匹配策略