feat(盯盘): 筛选/排序控制条——信号多选chips+排序下拉(RR升降/涨跌升降)+RR下限过滤+重置

This commit is contained in:
hmo
2026-07-22 09:31:19 +08:00
parent fa163f53f9
commit a42e3a16e5
+80 -2
View File
@@ -94,6 +94,28 @@ body { background: #0f0f13; color: #e2e8f0; }
<h2 class="text-lg font-semibold text-slate-200">👁️ 盯盘 · 全部策略</h2>
<span class="text-xs text-slate-500"><span id="watchRefreshTime"></span> <span id="watchCount" class="text-yellow-400 font-mono">0</span></span>
</div>
<!-- 筛选/排序控制条 -->
<div class="mb-3 p-2 rounded-lg bg-slate-800/40 border border-slate-700/50 text-xs space-y-2">
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-slate-500 mr-1">信号:</span>
<span id="watchSigChips"></span>
<button onclick="watchSigAll(true)" class="px-1.5 py-0.5 rounded text-slate-400 hover:text-white border border-slate-600">全选</button>
<button onclick="watchSigAll(false)" class="px-1.5 py-0.5 rounded text-slate-400 hover:text-white border border-slate-600">清空</button>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="text-slate-500">排序:</span>
<select id="watchSortMode" onchange="renderWatch()" class="bg-slate-900 border border-slate-600 rounded px-1.5 py-0.5 text-slate-300">
<option value="default">默认(分组)</option>
<option value="rr_desc">RR 从高到低</option>
<option value="rr_asc">RR 从低到高</option>
<option value="chg_desc">涨幅从高到低</option>
<option value="chg_asc">跌幅从高到低</option>
</select>
<span class="text-slate-500 ml-2">RR ≥</span>
<input id="watchMinRR" type="number" step="0.1" min="0" placeholder="0" onchange="renderWatch()" class="w-16 bg-slate-900 border border-slate-600 rounded px-1.5 py-0.5 text-slate-300">
<button onclick="watchResetFilters()" class="px-2 py-0.5 rounded bg-slate-700 text-slate-300 hover:bg-slate-600 ml-1">重置</button>
</div>
</div>
<div id="watchContent" class="card overflow-hidden"></div>
</div>
</div>
@@ -1368,18 +1390,74 @@ function fmtFullStrategy(s) {
return lines.join('\n');
}
// ── 盯盘筛选/排序状态 ──
const WATCH_SIGNALS = ['买入','可买入','可加仓','卖出','止盈','关注','观望','持有','弱势持有','信号不充分'];
let watchSigFilter = new Set(WATCH_SIGNALS); // 默认全选
let watchChipsInit = false;
function watchRenderChips() {
const host = document.getElementById('watchSigChips');
if (!host) return;
host.innerHTML = WATCH_SIGNALS.map(sig => {
const on = watchSigFilter.has(sig);
const cls = on
? 'bg-amber-500/25 text-amber-300 border-amber-500/50'
: 'bg-slate-800/50 text-slate-500 border-slate-700';
return `<button onclick="watchToggleSig('${sig}')" class="px-1.5 py-0.5 rounded border ${cls} transition">${sig}</button>`;
}).join('');
}
function watchToggleSig(sig) {
if (watchSigFilter.has(sig)) watchSigFilter.delete(sig);
else watchSigFilter.add(sig);
watchRenderChips();
renderWatch();
}
function watchSigAll(all) {
watchSigFilter = all ? new Set(WATCH_SIGNALS) : new Set();
watchRenderChips();
renderWatch();
}
function watchResetFilters() {
watchSigFilter = new Set(WATCH_SIGNALS);
document.getElementById('watchSortMode').value = 'default';
document.getElementById('watchMinRR').value = '';
watchRenderChips();
renderWatch();
}
async function renderWatch() {
const el = document.getElementById('watchContent');
if (!el) return;
try {
if (watchTimer) clearInterval(watchTimer);
if (!watchChipsInit) { watchRenderChips(); watchChipsInit = true; }
const data = await fetchJSON('/api/watch');
const stocks = data.stocks || [];
let stocks = data.stocks || [];
// ── 筛选:信号多选 + RR下限 ──
const minRR = parseFloat(document.getElementById('watchMinRR')?.value) || 0;
stocks = stocks.filter(s => {
const sig = s.timing_signal || '信号不充分';
if (!watchSigFilter.has(sig)) return false;
if (minRR > 0 && (s.rr_ratio || 0) < minRR) return false;
return true;
});
// ── 排序:默认=服务端分组序;覆盖模式按选定键全组内排序 ──
const sortMode = document.getElementById('watchSortMode')?.value || 'default';
const sorters = {
rr_desc: (a, b) => (b.rr_ratio || 0) - (a.rr_ratio || 0),
rr_asc: (a, b) => (a.rr_ratio || 0) - (b.rr_ratio || 0),
chg_desc: (a, b) => (b.change_pct || 0) - (a.change_pct || 0),
chg_asc: (a, b) => (a.change_pct || 0) - (b.change_pct || 0),
};
if (sorters[sortMode]) stocks = [...stocks].sort(sorters[sortMode]);
document.getElementById('watchCount').textContent = stocks.length;
document.getElementById('watchRefreshTime').textContent = new Date().toLocaleTimeString();
if (stocks.length === 0) {
el.innerHTML = '<div class="p-6 text-center text-slate-500 text-sm">暂无策略数据</div>';
el.innerHTML = '<div class="p-6 text-center text-slate-500 text-sm">当前筛选条件下暂无个股(调整筛选或点重置)</div>';
return;
}