404872007f
Co-authored-by: Cursor <cursoragent@cursor.com>
217 lines
8.0 KiB
JavaScript
217 lines
8.0 KiB
JavaScript
(function () {
|
|
function formatSub(item) {
|
|
return '同花顺 ' + item.ths_code +
|
|
(item.market_code ? ' · ' + item.market_code : '') +
|
|
' · ' + (item.exchange || '');
|
|
}
|
|
|
|
function formatInputLabel(item) {
|
|
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"]');
|
|
const hiddenName = wrapper.querySelector('input[name="symbol_name"]');
|
|
const hiddenMarket = wrapper.querySelector('input[name="market_code"]');
|
|
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');
|
|
}
|
|
|
|
function selectItem(item) {
|
|
const label = formatInputLabel(item);
|
|
input.value = label;
|
|
hiddenThs.value = item.ths_code;
|
|
hiddenName.value = item.name;
|
|
if (hiddenMarket) hiddenMarket.value = item.market_code || '';
|
|
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) {
|
|
dropdown.innerHTML = '';
|
|
if (!items.length) {
|
|
dropdown.innerHTML = '<div class="symbol-option">无匹配,可输入同花顺代码如 ag2608</div>';
|
|
} else {
|
|
items.forEach(function (item) {
|
|
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));
|
|
return;
|
|
}
|
|
if (abortCtrl) {
|
|
abortCtrl.abort();
|
|
}
|
|
abortCtrl = new AbortController();
|
|
fetch('/api/symbols/search?q=' + encodeURIComponent(q), {
|
|
signal: abortCtrl.signal,
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (items) {
|
|
cache.set(q, items);
|
|
renderItems(items);
|
|
})
|
|
.catch(function (err) {
|
|
if (err && err.name === 'AbortError') return;
|
|
hideDropdown();
|
|
});
|
|
}
|
|
|
|
input.addEventListener('input', function () {
|
|
hiddenThs.value = '';
|
|
hiddenName.value = '';
|
|
if (hiddenMarket) hiddenMarket.value = '';
|
|
if (hiddenSina) hiddenSina.value = '';
|
|
selectedEl.textContent = '';
|
|
const q = input.value.trim();
|
|
if (!q) {
|
|
if (isMarketPicker) {
|
|
showMarketMains('');
|
|
} else {
|
|
hideDropdown();
|
|
}
|
|
return;
|
|
}
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
if (isMarketPicker) {
|
|
showMarketMains(q);
|
|
} else {
|
|
search(q);
|
|
}
|
|
}, 120);
|
|
});
|
|
|
|
input.addEventListener('blur', function () {
|
|
setTimeout(hideDropdown, 150);
|
|
});
|
|
|
|
input.addEventListener('focus', function () {
|
|
const q = input.value.trim();
|
|
if (isMarketPicker) {
|
|
showMarketMains(q);
|
|
return;
|
|
}
|
|
if (q && !hiddenThs.value) {
|
|
search(q);
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
document.querySelectorAll('.symbol-wrap').forEach(initSymbolInput);
|
|
|
|
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"]');
|
|
if (ths && !ths.value.trim()) {
|
|
e.preventDefault();
|
|
alert('请从下拉列表选择品种');
|
|
return;
|
|
}
|
|
if (market && !market.value.trim()) {
|
|
e.preventDefault();
|
|
alert('请从下拉列表选择品种(需含同花顺行情代码)');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
})();
|