diff --git a/static/js/symbol.js b/static/js/symbol.js index 3514688..596bcc3 100644 --- a/static/js/symbol.js +++ b/static/js/symbol.js @@ -1,4 +1,35 @@ (function () { + var recommendedGroupsCache = null; + var recommendedGroupsPromise = null; + + function loadRecommendedGroups() { + if (recommendedGroupsCache) { + return Promise.resolve(recommendedGroupsCache); + } + if (recommendedGroupsPromise) { + return recommendedGroupsPromise; + } + recommendedGroupsPromise = fetch('/api/symbols/recommended') + .then(function (r) { + if (!r.ok) { + throw new Error('HTTP ' + r.status); + } + return r.json(); + }) + .then(function (groups) { + recommendedGroupsCache = Array.isArray(groups) ? groups : []; + return recommendedGroupsCache; + }) + .catch(function () { + recommendedGroupsCache = null; + throw new Error('load failed'); + }) + .finally(function () { + recommendedGroupsPromise = null; + }); + return recommendedGroupsPromise; + } + function formatSub(item) { var sub = '同花顺 ' + item.ths_code + (item.market_code ? ' · ' + item.market_code : '') + @@ -47,7 +78,6 @@ let abortCtrl = null; const cache = new Map(); let mainsCache = null; - let mainsLoading = false; function hideDropdown() { dropdown.classList.remove('show'); @@ -135,16 +165,9 @@ renderGrouped(mainsCache, q); return; } - if (mainsLoading) { - dropdown.innerHTML = '
正在加载推荐品种…
'; - dropdown.classList.add('show'); - return; - } - mainsLoading = true; dropdown.innerHTML = '
正在加载推荐品种…
'; dropdown.classList.add('show'); - fetch('/api/symbols/recommended') - .then(function (r) { return r.json(); }) + loadRecommendedGroups() .then(function (groups) { mainsCache = groups; if (!groups.length) { @@ -156,10 +179,9 @@ showMarketMains(filterQ, onEmpty); }) .catch(function () { - hideDropdown(); - }) - .finally(function () { - mainsLoading = false; + dropdown.innerHTML = + '
推荐品种加载失败,请刷新页面或输入合约代码搜索
'; + dropdown.classList.add('show'); }); } diff --git a/symbols.py b/symbols.py index 3ed0dde..0ba344e 100644 --- a/symbols.py +++ b/symbols.py @@ -431,15 +431,42 @@ def _product_for_ths(ths: str) -> Optional[dict]: return _THS_TO_PRODUCT.get(key) or _THS_TO_PRODUCT.get(key.lower()) -def _main_for_product(product: dict) -> Optional[dict]: +def _item_from_recommend_row(row: dict, product: dict) -> Optional[dict]: + """由推荐缓存行快速构造下拉项(不在 HTTP 请求中解析主力)。""" + name = row.get("name") or product["name"] + main_code = (row.get("main_code") or "").strip() + max_lots = row.get("max_lots") + + if main_code: + codes = ths_to_codes(main_code) + if codes: + ths = codes["ths_code"] + item = { + "name": name, + "ths_code": ths, + "market_code": codes.get("market_code") or "", + "sina_code": codes.get("sina_code") or "", + "exchange": product["exchange"], + "contract": f"主力 {ths}", + "display": f"{name} 主力 {ths}", + "input_label": f"{name} {ths}", + } + if max_lots is not None: + item["max_lots"] = max_lots + return _enrich_item(item) + with _main_index_lock: - index = dict(_main_index) - main = index.get(product["sina"]) - if not main: - resolved = resolve_main_contract(product) - if resolved: - main = _enrich_item(resolved) - return main + main = _main_index.get(product["sina"]) + if main: + item = dict(main) + if max_lots is not None: + item["max_lots"] = max_lots + return _enrich_item(item) + + item = _stub_main_contract(product) + if max_lots is not None: + item["max_lots"] = max_lots + return item def list_recommended_symbols_grouped(recommend_rows: list[dict]) -> list[dict]: @@ -459,14 +486,10 @@ def list_recommended_symbols_grouped(recommend_rows: list[dict]) -> list[dict]: if not product: continue seen.add(ths_key) - main = _main_for_product(product) - if not main: + item = _item_from_recommend_row(row, product) + if not item: continue - item = dict(main) - max_lots = row.get("max_lots") - if max_lots is not None: - item["max_lots"] = max_lots - buckets[product["exchange"]].append(_enrich_item(item)) + buckets[product["exchange"]].append(item) groups: list[dict] = [] for cat in EXCHANGE_ORDER: