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: