行情K线:分类主力选择、图表指标与布局稳定

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-15 18:23:39 +08:00
parent 65992eb35e
commit 404872007f
8 changed files with 644 additions and 114 deletions
+96 -11
View File
@@ -9,6 +9,16 @@
return item.input_label || (item.name + ' ' + item.ths_code);
}
function itemMatchesQuery(item, qLower) {
if (!qLower) return true;
var hay = (
item.name + ' ' + item.ths_code + ' ' +
(item.display || '') + ' ' + (item.contract || '') + ' ' +
(item.exchange || '')
).toLowerCase();
return hay.indexOf(qLower) >= 0;
}
function initSymbolInput(wrapper) {
const input = wrapper.querySelector('.symbol-input');
const hiddenThs = wrapper.querySelector('input[name="symbol"]');
@@ -17,9 +27,12 @@
const hiddenSina = wrapper.querySelector('input[name="sina_code"]');
const dropdown = wrapper.querySelector('.symbol-dropdown');
const selectedEl = wrapper.querySelector('.symbol-selected');
const isMarketPicker = wrapper.classList.contains('market-symbol-wrap');
let timer = null;
let abortCtrl = null;
const cache = new Map();
let mainsCache = null;
let mainsLoading = false;
function hideDropdown() {
dropdown.classList.remove('show');
@@ -34,6 +47,28 @@
if (hiddenSina) hiddenSina.value = item.sina_code || '';
selectedEl.textContent = formatSub(item);
hideDropdown();
if (isMarketPicker) {
input.dispatchEvent(new CustomEvent('symbol-selected', { detail: item }));
}
}
function buildOptionEl(item) {
const div = document.createElement('div');
div.className = 'symbol-option';
if (item.near_expiry) {
div.classList.add('near-expiry');
}
var label = item.display || (item.name + ' ' + item.ths_code);
if (item.near_expiry) {
label += ' <span class="near-expiry-tag">临期</span>';
}
div.innerHTML = label +
'<div class="sub">' + formatSub(item) + '</div>';
div.addEventListener('mousedown', function (e) {
e.preventDefault();
selectItem(item);
});
return div;
}
function renderItems(items) {
@@ -42,20 +77,57 @@
dropdown.innerHTML = '<div class="symbol-option">无匹配,可输入同花顺代码如 ag2608</div>';
} else {
items.forEach(function (item) {
const div = document.createElement('div');
div.className = 'symbol-option';
div.innerHTML = item.display +
'<div class="sub">' + formatSub(item) + '</div>';
div.addEventListener('mousedown', function (e) {
e.preventDefault();
selectItem(item);
});
dropdown.appendChild(div);
dropdown.appendChild(buildOptionEl(item));
});
}
dropdown.classList.add('show');
}
function renderGrouped(groups, filterQ) {
dropdown.innerHTML = '';
const qLower = (filterQ || '').trim().toLowerCase();
let any = false;
groups.forEach(function (group) {
const items = group.items.filter(function (item) {
return itemMatchesQuery(item, qLower);
});
if (!items.length) return;
any = true;
const head = document.createElement('div');
head.className = 'symbol-group-head';
head.textContent = group.category;
dropdown.appendChild(head);
items.forEach(function (item) {
dropdown.appendChild(buildOptionEl(item));
});
});
if (!any) {
dropdown.innerHTML = '<div class="symbol-option">无匹配品种,可输入合约代码如 ag2608</div>';
}
dropdown.classList.add('show');
}
function showMarketMains(filterQ) {
if (mainsCache) {
renderGrouped(mainsCache, filterQ);
return;
}
if (mainsLoading) return;
mainsLoading = true;
fetch('/api/symbols/mains')
.then(function (r) { return r.json(); })
.then(function (groups) {
mainsCache = groups;
renderGrouped(groups, filterQ);
})
.catch(function () {
hideDropdown();
})
.finally(function () {
mainsLoading = false;
});
}
function search(q) {
if (cache.has(q)) {
renderItems(cache.get(q));
@@ -87,12 +159,20 @@
selectedEl.textContent = '';
const q = input.value.trim();
if (!q) {
hideDropdown();
if (isMarketPicker) {
showMarketMains('');
} else {
hideDropdown();
}
return;
}
clearTimeout(timer);
timer = setTimeout(function () {
search(q);
if (isMarketPicker) {
showMarketMains(q);
} else {
search(q);
}
}, 120);
});
@@ -102,6 +182,10 @@
input.addEventListener('focus', function () {
const q = input.value.trim();
if (isMarketPicker) {
showMarketMains(q);
return;
}
if (q && !hiddenThs.value) {
search(q);
}
@@ -113,6 +197,7 @@
document.querySelectorAll('form').forEach(function (form) {
if (!form.querySelector('.symbol-wrap')) return;
if (form.id === 'market-form') return;
form.addEventListener('submit', function (e) {
const ths = form.querySelector('input[name="symbol"]');
const market = form.querySelector('input[name="market_code"]');