Add industry filter to recommendations and fix verify button width.

Show category, turnover, and per-industry counts; clarify volume is in lots. Prevent trade-save button from stretching full column width.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-26 01:21:53 +08:00
parent e5f675c6ca
commit aad88a9e98
9 changed files with 184 additions and 14 deletions
+99 -6
View File
@@ -33,8 +33,11 @@
var recRowsRaw = [];
var recSortKey = 'trend';
var recSortDesc = true;
var REC_SORT_CACHE = 'qihuo_rec_sort_v1';
var REC_COLSPAN = 14;
var recIndustryFilter = '';
var REC_SORT_CACHE = 'qihuo_rec_sort_v2';
var REC_INDUSTRY_CACHE = 'qihuo_rec_industry_v1';
var REC_COLSPAN = 16;
var productCategories = window.PRODUCT_CATEGORIES || [];
var POS_CACHE_KEY = 'qihuo_trading_live_v3';
function runWhenReady(fn) {
@@ -1107,6 +1110,10 @@
if (p.key) recSortKey = p.key;
if (typeof p.desc === 'boolean') recSortDesc = p.desc;
} catch (e) { /* ignore */ }
try {
var ind = sessionStorage.getItem(REC_INDUSTRY_CACHE);
if (ind != null) recIndustryFilter = ind;
} catch (e2) { /* ignore */ }
}
function saveRecSortPrefs() {
@@ -1115,11 +1122,62 @@
} catch (e) { /* ignore */ }
}
function saveRecIndustryPref() {
try {
sessionStorage.setItem(REC_INDUSTRY_CACHE, recIndustryFilter || '');
} catch (e) { /* ignore */ }
}
function syncRecSortUi() {
var sel = document.getElementById('rec-sort-key');
var btn = document.getElementById('rec-sort-dir');
var indSel = document.getElementById('rec-industry-filter');
if (sel) sel.value = recSortKey;
if (btn) btn.textContent = recSortDesc ? '↓' : '↑';
if (indSel) indSel.value = recIndustryFilter || '';
}
function filterRecommendRows(rows) {
if (!recIndustryFilter) return (rows || []).slice();
return (rows || []).filter(function (r) {
return (r.category || '') === recIndustryFilter;
});
}
function countByCategory(rows) {
var counts = {};
(rows || []).forEach(function (r) {
var cat = r.category || '其他';
counts[cat] = (counts[cat] || 0) + 1;
});
return counts;
}
function updateRecStats(allRows, visibleRows) {
var el = document.getElementById('rec-stats');
if (!el) return;
var total = (allRows || []).length;
var shown = (visibleRows || []).length;
if (!total) {
el.textContent = '';
return;
}
var parts = [];
if (recIndustryFilter) {
parts.push('筛选 <strong>' + shown + '</strong> / 共 ' + total + ' 个品种');
} else {
parts.push('共 <strong>' + total + '</strong> 个品种');
}
var order = productCategories.length ? productCategories.slice() : [];
var counts = countByCategory(recIndustryFilter ? visibleRows : allRows);
Object.keys(counts).forEach(function (k) {
if (order.indexOf(k) < 0) order.push(k);
});
var breakdown = order.filter(function (cat) { return counts[cat]; }).map(function (cat) {
return cat + ' ' + counts[cat];
});
if (breakdown.length) parts.push(breakdown.join(' · '));
el.innerHTML = parts.join(' · ');
}
var TREND_SORT_RANK = { break_long: 0, break_short: 0, long: 1, short: 2, range: 3, '': 9 };
@@ -1165,6 +1223,15 @@
return String(Math.round(n));
}
function fmtRecTurnover(v) {
if (v === null || v === undefined) return '—';
var n = Number(v);
if (!isFinite(n)) return '—';
if (n >= 1e8) return (n / 1e8).toFixed(2) + '亿';
if (n >= 1e4) return (n / 1e4).toFixed(1) + '万';
return String(Math.round(n));
}
function changeCellHtml(r) {
if (r.yesterday_change == null) return '—';
var ch = Number(r.yesterday_change);
@@ -1206,7 +1273,10 @@
function renderRecommendRows(rows) {
if (!recommendList) return;
if (!rows.length) {
recommendList.innerHTML = '<tr><td colspan="' + REC_COLSPAN + '" class="empty-hint">当前资金下暂无推荐品种(每日后台刷新)</td></tr>';
var emptyMsg = recIndustryFilter
? '当前行业下暂无推荐品种'
: '当前资金下暂无推荐品种(每日后台刷新)';
recommendList.innerHTML = '<tr><td colspan="' + REC_COLSPAN + '" class="empty-hint">' + emptyMsg + '</td></tr>';
return;
}
recommendList.innerHTML = rows.map(function (r) {
@@ -1217,6 +1287,7 @@
'<tr class="' + rowCls + '">' +
'<td><strong' + nameCls + '>' + (r.name || '') + '</strong> <span class="text-accent">' + (r.main_code || r.ths || '') + '</span></td>' +
'<td>' + (r.exchange || '') + '</td>' +
'<td>' + (r.category || '—') + '</td>' +
'<td>' + trendBadgeHtml(r) + '</td>' +
'<td>' + gapBadgeHtml(r) + '</td>' +
'<td>' + (r.price != null ? r.price : '—') + '</td>' +
@@ -1225,6 +1296,7 @@
'<td>' + changeCellHtml(r) + '</td>' +
'<td>' + (r.yesterday_amplitude_pct != null ? r.yesterday_amplitude_pct + '%' : '—') + '</td>' +
'<td>' + fmtRecVolume(r.volume) + '</td>' +
'<td>' + fmtRecTurnover(r.turnover) + '</td>' +
'<td>' + (r.margin_one_lot != null ? r.margin_one_lot + (r.margin_source === 'ctp' ? ' <span class="text-muted">(柜台)</span>' : '') : '—') + '</td>' +
'<td>' + (r.open_fee_one_lot != null ? r.open_fee_one_lot : '—') + '</td>' +
'<td>' + (r.max_lots != null && r.max_lots > 0 ? r.max_lots : '—') + '</td>' +
@@ -1234,6 +1306,13 @@
}).join('');
}
function renderRecommendTable() {
var filtered = filterRecommendRows(recRowsRaw);
var sorted = sortRecommendRows(filtered);
updateRecStats(recRowsRaw, sorted);
renderRecommendRows(sorted);
}
function renderRecommendations(data) {
if (!recommendList || !data) return;
updateRecommendMaxMaps(data);
@@ -1247,9 +1326,10 @@
recRowsRaw = rows.slice();
if (!rows.length) {
recommendList.innerHTML = '<tr><td colspan="' + REC_COLSPAN + '" class="empty-hint">当前资金下暂无推荐品种(每日后台刷新)</td></tr>';
updateRecStats([], []);
return;
}
renderRecommendRows(sortRecommendRows(recRowsRaw));
renderRecommendTable();
}
function initRecommendSortControls() {
@@ -1257,11 +1337,19 @@
syncRecSortUi();
var sel = document.getElementById('rec-sort-key');
var btn = document.getElementById('rec-sort-dir');
var indSel = document.getElementById('rec-industry-filter');
if (indSel) {
indSel.addEventListener('change', function () {
recIndustryFilter = indSel.value || '';
saveRecIndustryPref();
renderRecommendTable();
});
}
if (sel) {
sel.addEventListener('change', function () {
recSortKey = sel.value || 'trend';
saveRecSortPrefs();
renderRecommendRows(sortRecommendRows(recRowsRaw));
renderRecommendTable();
});
}
if (btn) {
@@ -1269,9 +1357,10 @@
recSortDesc = !recSortDesc;
saveRecSortPrefs();
syncRecSortUi();
renderRecommendRows(sortRecommendRows(recRowsRaw));
renderRecommendTable();
});
}
if (recRowsRaw.length) updateRecStats(recRowsRaw, filterRecommendRows(recRowsRaw));
}
function connectRecommendStream() {
@@ -1373,6 +1462,10 @@
initCtpOnLoad();
connectRecommendStream();
initRecommendSortControls();
if (window.__RECOMMEND_ROWS__ && window.__RECOMMEND_ROWS__.length) {
recRowsRaw = window.__RECOMMEND_ROWS__.slice();
renderRecommendTable();
}
fetch('/api/recommend/list')
.then(function (r) { return r.json(); })
.then(function (data) { if (data.ok) renderRecommendations(data); })