diff --git a/static/js/settings.js b/static/js/settings.js new file mode 100644 index 0000000..8e684f2 --- /dev/null +++ b/static/js/settings.js @@ -0,0 +1,126 @@ +/* Copyright (c) 2025-2026 马建军. All rights reserved. + * 专有软件 — 未经授权禁止复制、传播、转售。 + * 详见 LICENSE.zh-CN.txt + */ +(function () { + function bootSettingsPage() { + if (!document.querySelector('.settings-page')) return; + + var sel = document.getElementById('position-sizing-mode'); + var lotsField = document.getElementById('field-fixed-lots'); + var amountField = document.getElementById('field-fixed-amount'); + function syncSizingFields() { + if (!sel) return; + var isAmount = sel.value === 'amount'; + if (lotsField) lotsField.hidden = isAmount; + if (amountField) amountField.hidden = !isAmount; + } + if (sel && !sel.dataset.settingsBound) { + sel.dataset.settingsBound = '1'; + sel.addEventListener('change', syncSizingFields); + } + syncSizingFields(); + + var SETTINGS_FOLD_KEY = 'qihuo_settings_fold'; + function setSettingsFold(el, collapsed) { + if (!el) return; + el.classList.toggle('is-collapsed', collapsed); + var head = el.querySelector('.settings-fold-head'); + if (head) head.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); + } + function saveSettingsFoldState() { + var state = {}; + document.querySelectorAll('[data-settings-fold]').forEach(function (el) { + state[el.getAttribute('data-settings-fold')] = el.classList.contains('is-collapsed'); + }); + try { localStorage.setItem(SETTINGS_FOLD_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ } + } + function loadSettingsFoldState() { + try { + var raw = localStorage.getItem(SETTINGS_FOLD_KEY); + if (!raw) return; + var state = JSON.parse(raw); + document.querySelectorAll('[data-settings-fold]').forEach(function (el) { + var key = el.getAttribute('data-settings-fold'); + if (Object.prototype.hasOwnProperty.call(state, key)) { + setSettingsFold(el, !!state[key]); + } + }); + } catch (e) { /* ignore */ } + } + document.querySelectorAll('.settings-fold-head').forEach(function (btn) { + if (btn.dataset.settingsBound) return; + btn.dataset.settingsBound = '1'; + btn.addEventListener('click', function () { + var panel = btn.closest('[data-settings-fold]'); + if (!panel) return; + setSettingsFold(panel, !panel.classList.contains('is-collapsed')); + saveSettingsFoldState(); + }); + }); + loadSettingsFoldState(); + + var CTP_FOLD_KEY = 'qihuo_ctp_fold'; + function setCtpFold(el, collapsed) { + if (!el) return; + el.classList.toggle('is-collapsed', collapsed); + var head = el.querySelector('.settings-ctp-fold-head'); + if (head) head.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); + } + function saveCtpFoldState() { + var state = {}; + document.querySelectorAll('[data-ctp-fold]').forEach(function (el) { + state[el.getAttribute('data-ctp-fold')] = el.classList.contains('is-collapsed'); + }); + try { localStorage.setItem(CTP_FOLD_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ } + } + function loadCtpFoldState() { + try { + var raw = localStorage.getItem(CTP_FOLD_KEY); + if (!raw) return; + var state = JSON.parse(raw); + document.querySelectorAll('[data-ctp-fold]').forEach(function (el) { + var key = el.getAttribute('data-ctp-fold'); + if (Object.prototype.hasOwnProperty.call(state, key)) { + setCtpFold(el, !!state[key]); + } + }); + } catch (e) { /* ignore */ } + } + document.querySelectorAll('.settings-ctp-fold-head').forEach(function (btn) { + if (btn.dataset.settingsBound) return; + btn.dataset.settingsBound = '1'; + btn.addEventListener('click', function () { + var panel = btn.closest('[data-ctp-fold]'); + if (!panel) return; + setCtpFold(panel, !panel.classList.contains('is-collapsed')); + saveCtpFoldState(); + }); + }); + loadCtpFoldState(); + + var ctpForm = document.getElementById('ctp-settings-form'); + if (ctpForm && !ctpForm.dataset.settingsBound) { + ctpForm.dataset.settingsBound = '1'; + ctpForm.addEventListener('submit', function (ev) { + var ctpCard = document.querySelector('[data-settings-fold="ctp"]'); + if (ctpCard) setSettingsFold(ctpCard, false); + var simnowFold = document.querySelector('[data-ctp-fold="simnow"]'); + if (simnowFold) setCtpFold(simnowFold, false); + var pwd = document.getElementById('simnow_password'); + var pwdVal = pwd && pwd.value ? pwd.value.trim() : ''; + var pwdWasSet = ctpForm.getAttribute('data-simnow-pwd-set') === '1'; + if (pwdWasSet && !pwdVal) { + var ok = window.confirm( + 'SimNow 交易密码为空,保存后不会更新密码(仍用旧密码)。\n\n' + + '若快期已改密,请取消后在「交易密码」框手打新密码再保存。\n\n仍要保存其他项?' + ); + if (!ok) ev.preventDefault(); + } + }); + } + } + + if (window.qihuoPageBoot) window.qihuoPageBoot(bootSettingsPage, '.settings-page'); + else document.addEventListener('DOMContentLoaded', bootSettingsPage); +})(); diff --git a/static/js/stats.js b/static/js/stats.js index 1eff91b..e278365 100644 --- a/static/js/stats.js +++ b/static/js/stats.js @@ -138,8 +138,11 @@ } function loadStats() { - fetch('/api/stats') - .then(function (r) { return r.json(); }) + fetch('/api/stats', { credentials: 'same-origin' }) + .then(function (r) { + if (!r.ok) throw new Error('HTTP ' + r.status); + return r.json(); + }) .then(applyData) .catch(function () { var updated = document.getElementById('stats-updated'); @@ -148,6 +151,7 @@ } function bootStatsPage() { + if (!document.getElementById('stats-summary')) return; var viewSel = document.getElementById('stats-view-select'); if (viewSel && !viewSel.dataset.statsBound) { viewSel.dataset.statsBound = '1'; diff --git a/static/js/trade.js b/static/js/trade.js index fafbd9d..11d5cc3 100644 --- a/static/js/trade.js +++ b/static/js/trade.js @@ -3,8 +3,7 @@ * 详见 LICENSE.zh-CN.txt */ (function () { - var sizingMode = window.TRADE_SIZING_MODE || 'fixed'; - if (sizingMode === 'risk') sizingMode = 'amount'; + var sizingMode = 'fixed'; var list = document.getElementById('position-live-list'); var orderList = document.getElementById('order-live-list'); var syncBadge = document.getElementById('sync-badge'); @@ -33,7 +32,7 @@ var hasSlTpMonitoring = false; var ctpConnected = false; var ctpConnecting = false; - var ctpAutoConnectEnabled = window.CTP_AUTO_CONNECT !== false; + var ctpAutoConnectEnabled = true; var positionsRendered = false; var selectedMaxLots = null; var recommendMaxByProduct = {}; @@ -45,10 +44,26 @@ var REC_SORT_CACHE = 'qihuo_rec_sort_v2'; var REC_INDUSTRY_CACHE = 'qihuo_rec_industry_v1'; var REC_COLSPAN = 18; - var marketNavEnabled = !!window.MARKET_NAV_ENABLED; - var productCategories = window.PRODUCT_CATEGORIES || []; + var marketNavEnabled = false; + var productCategories = []; var POS_CACHE_KEY = 'qihuo_trading_live_v5'; + function loadTradeConfig() { + var el = document.getElementById('trade-page-data'); + if (!el) return; + try { + var cfg = JSON.parse(el.textContent); + sizingMode = cfg.sizing_mode || 'fixed'; + if (sizingMode === 'risk') sizingMode = 'amount'; + ctpAutoConnectEnabled = cfg.ctp_auto_connect !== false; + marketNavEnabled = !!cfg.market_nav_enabled; + productCategories = cfg.product_categories || []; + window.TRADE_FIXED_LOTS = cfg.fixed_lots; + window.TRADE_FIXED_AMOUNT = cfg.fixed_amount; + window.__RECOMMEND_ROWS__ = cfg.recommend_rows || []; + } catch (e) { /* ignore */ } + } + function fmtNum(v, digits) { if (v === null || v === undefined) return '--'; return Number(v).toFixed(digits === undefined ? 2 : digits); @@ -1607,6 +1622,7 @@ } function bootTradePage() { + loadTradeConfig(); if (!list && !orderList) return; updateCtpConnectButtonState(); setPriceType('limit'); @@ -1648,7 +1664,7 @@ } }); - if (window.qihuoPageBoot) window.qihuoPageBoot(bootTradePage, '#position-live-list, #order-live-list'); + if (window.qihuoPageBoot) window.qihuoPageBoot(bootTradePage, '.trade-page'); else if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootTradePage); else bootTradePage(); if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupTradePage); diff --git a/static/js/turbonav.js b/static/js/turbonav.js index 1b17818..368c3b0 100644 --- a/static/js/turbonav.js +++ b/static/js/turbonav.js @@ -4,7 +4,7 @@ */ (function () { var PERMANENT_CSS = ['/static/css/base.css', '/static/css/tech.css', '/static/css/responsive.css']; - var CORE_SCRIPT_MARKERS = ['theme.js', 'symbol.js', 'page.js', 'nav.js', 'pwa.js', 'turbonav.js']; + var CORE_SCRIPT_RE = /theme\.js|symbol\.js|page\.js|nav\.js|pwa\.js|turbonav\.js/; var pageCache = new Map(); var MAX_CACHE = 10; var inflight = null; @@ -21,12 +21,6 @@ return PERMANENT_CSS.some(function (p) { return href.indexOf(p) !== -1; }); } - function isCoreScript(el) { - var src = el.getAttribute('src') || ''; - if (!src) return false; - return CORE_SCRIPT_MARKERS.some(function (m) { return src.indexOf(m) !== -1; }); - } - function shouldTurboClick(e, link) { if (!link || !link.href) return false; if (e.defaultPrevented) return false; @@ -55,13 +49,13 @@ if (!res.ok) throw new Error('HTTP ' + res.status); return res.text(); }).then(function (html) { - var doc = parseHtml(html); - pageCache.set(key, doc); + var pack = { doc: parseHtml(html), html: html }; + pageCache.set(key, pack); if (pageCache.size > MAX_CACHE) { var first = pageCache.keys().next().value; pageCache.delete(first); } - return doc; + return pack; }); } @@ -84,24 +78,42 @@ return fetchedMain.innerHTML; } - function collectPageScripts(doc) { - var out = []; - var pastCore = false; - doc.body.querySelectorAll(':scope > script').forEach(function (el) { - if (isCoreScript(el)) { - if ((el.getAttribute('src') || '').indexOf('pwa.js') !== -1) pastCore = true; - return; + /** DOMParser strips inline script bodies — parse from raw HTML instead. */ + function collectPageScripts(rawHtml) { + var scripts = []; + var bodyMatch = /([\s\S]*)<\/body>/i.exec(rawHtml); + if (!bodyMatch) return scripts; + var body = bodyMatch[1]; + var re = /]*)?>([\s\S]*?)<\/script>/gi; + var pastPwa = false; + var m; + while ((m = re.exec(body)) !== null) { + var attrs = m[1] || ''; + var text = m[2]; + var srcMatch = /\ssrc=["']([^"']+)["']/i.exec(attrs); + var typeMatch = /\stype=["']([^"']+)["']/i.exec(attrs); + var idMatch = /\sid=["']([^"']+)["']/i.exec(attrs); + var src = srcMatch ? srcMatch[1] : ''; + var type = typeMatch ? typeMatch[1].toLowerCase() : 'text/javascript'; + var id = idMatch ? idMatch[1] : ''; + if (src) { + if (/pwa\.js/.test(src)) pastPwa = true; + if (CORE_SCRIPT_RE.test(src)) continue; + if (!pastPwa) continue; + scripts.push({ src: src, text: '', type: type, id: id }); + continue; } - if (!pastCore) return; - if (isCoreScript(el)) return; - out.push(el); - }); - return out; + if (!pastPwa) continue; + scripts.push({ src: '', text: text, type: type, id: id }); + } + return scripts; } function removePageAssets() { document.querySelectorAll('[data-page-css]').forEach(function (el) { el.remove(); }); - document.querySelectorAll('body script[data-page-js]').forEach(function (el) { el.remove(); }); + document.querySelectorAll('body script[data-page-js], body script[data-page-data]').forEach(function (el) { + el.remove(); + }); } function applyPageCss(items) { @@ -124,24 +136,37 @@ } function runPageScripts(items) { - return items.reduce(function (chain, srcEl) { + return items.reduce(function (chain, item) { return chain.then(function () { return new Promise(function (resolve) { var s = document.createElement('script'); - s.setAttribute('data-page-js', ''); - var src = srcEl.getAttribute('src'); - if (src) { - var bust = (src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now(); - s.src = src + bust; + if (item.src) { + s.setAttribute('data-page-js', ''); + var bust = (item.src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now(); + s.src = item.src + bust; s.async = false; s.onload = function () { resolve(); }; s.onerror = function () { resolve(); }; document.body.appendChild(s); - } else { - s.textContent = srcEl.textContent; + return; + } + if (item.type === 'application/json') { + s.type = 'application/json'; + if (item.id) s.id = item.id; + s.setAttribute('data-page-data', ''); + s.textContent = item.text; document.body.appendChild(s); resolve(); + return; } + if (item.type && item.type !== 'text/javascript' && item.type !== 'module') { + resolve(); + return; + } + s.setAttribute('data-page-js', ''); + s.textContent = item.text; + document.body.appendChild(s); + resolve(); }); }); }, Promise.resolve()); @@ -166,7 +191,9 @@ if (main) main.classList.toggle('nav-loading', on); } - function applyDocument(doc, url, fromPopstate) { + function applyDocument(pack, url, fromPopstate) { + var doc = pack.doc; + var rawHtml = pack.html; var main = document.querySelector('.main'); if (!main || !doc.querySelector('.main')) { window.location.href = url; @@ -178,12 +205,13 @@ var cssItems = collectPageCss(doc); var mainHtml = prepareMainHtml(doc); + var scriptItems = collectPageScripts(rawHtml); return applyPageCss(cssItems).then(function () { main.innerHTML = mainHtml; document.title = doc.title || document.title; syncNavActive(doc, url); - return runPageScripts(collectPageScripts(doc)); + return runPageScripts(scriptItems); }).then(function () { currentUrl = normalizeUrl(url); if (!fromPopstate) { @@ -205,10 +233,10 @@ inflight = ctrl; setLoading(true); - return fetchPage(target, ctrl.signal).then(function (doc) { + return fetchPage(target, ctrl.signal).then(function (pack) { if (ctrl.signal.aborted) return; inflight = null; - return applyDocument(doc, target, !!opts.fromPopstate); + return applyDocument(pack, target, !!opts.fromPopstate); }).catch(function () { if (ctrl.signal.aborted) return; inflight = null; diff --git a/static/sw.js b/static/sw.js index 318e04f..8ba0787 100644 --- a/static/sw.js +++ b/static/sw.js @@ -2,7 +2,7 @@ * 专有软件 — 未经授权禁止复制、传播、转售。 * 详见 LICENSE.zh-CN.txt */ -var CACHE_VERSION = 'qihuo-v7'; +var CACHE_VERSION = 'qihuo-v8'; var STATIC_CACHE = CACHE_VERSION + '-static'; var STATIC_ASSETS = [ '/static/css/base.css', diff --git a/templates/settings.html b/templates/settings.html index 5fdb34f..ed4005e 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -212,7 +212,7 @@ {% endif %}

-
+
@@ -478,113 +478,5 @@
{% endblock %} {% block extra_js %} - + {% endblock %} diff --git a/templates/trade.html b/templates/trade.html index 177f44d..23e60be 100644 --- a/templates/trade.html +++ b/templates/trade.html @@ -227,14 +227,14 @@ {% endblock %} {% block extra_js %} - + {% endblock %}