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 = / + {% 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 %}