From 690537340192f9ad50a91051cd34921a6a5e13b4 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 26 Jun 2026 13:10:43 +0800 Subject: [PATCH] Make settings cards collapsible and tighten backup layout. All settings sections fold by default with persisted state; backup and password sit in one compact row. Co-authored-by: Cursor --- templates/settings.html | 171 +++++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 44 deletions(-) diff --git a/templates/settings.html b/templates/settings.html index 231b59b..69d1d68 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -7,7 +7,9 @@ .settings-page .split-grid{margin-bottom:0} .settings-page .split-grid .card{margin-bottom:0;min-height:100%;height:100%;display:flex;flex-direction:column} .settings-page .split-grid .card > form, -.settings-page .split-grid .card > .card-inner{flex:1;display:flex;flex-direction:column} +.settings-page .split-grid .card > .card-inner, +.settings-page .split-grid .settings-fold-body > form, +.settings-page .split-grid .settings-fold-body > .card-inner{flex:1;display:flex;flex-direction:column} .settings-password-form{display:grid;grid-template-columns:1fr 1fr;gap:.65rem .75rem} .settings-password-form .field-full{grid-column:1/-1} .settings-password-form .field label{font-size:.78rem} @@ -67,7 +69,48 @@ .settings-backup-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem .65rem} .settings-backup-download{color:var(--accent);text-decoration:none;font-weight:600} .settings-backup-download:hover{text-decoration:underline} +.settings-admin-row .settings-compact-card{font-size:.78rem} +.settings-admin-row .settings-compact-card .hint, +.settings-admin-row .settings-backup-meta, +.settings-admin-row .settings-backup-restore{font-size:.72rem;line-height:1.5} +.settings-admin-row .settings-backup-table{font-size:.7rem} +.settings-admin-row .settings-backup-table th, +.settings-admin-row .settings-backup-table td{padding:.35rem .4rem} +.settings-admin-row .settings-backup-table td:first-child code{word-break:break-all;font-size:.68rem} +.settings-admin-row .field label{font-size:.72rem} +.settings-admin-row .field input{padding:.4rem .55rem;font-size:.78rem} +.settings-admin-row .settings-backup-config{display:grid;grid-template-columns:1fr;gap:.45rem;margin-bottom:.55rem} +.settings-admin-row .settings-backup-actions{margin-top:.35rem} +.settings-admin-row .settings-backup-actions .btn-primary, +.settings-admin-row .settings-compact-card > form .btn-primary{padding:.42rem .7rem;font-size:.78rem} +.settings-admin-row .settings-password-form{grid-template-columns:1fr;gap:.45rem .55rem} +.settings-admin-row .settings-password-form input{padding:.4rem .55rem;font-size:.78rem} +.settings-page .settings-fold.card{padding:0;overflow:hidden} +.settings-page .split-grid .settings-fold.card{min-height:auto;height:auto} +.settings-fold-head{ + width:100%;display:flex;align-items:center;justify-content:space-between;gap:.75rem; + padding:1rem 1rem .85rem;margin:0;border:none;background:transparent;cursor:pointer; + text-align:left; +} +.settings-fold-head:hover .settings-fold-title{color:var(--accent)} +.settings-fold-title{ + display:flex;align-items:center;gap:.5rem;font-size:1.15rem;font-weight:600; + color:var(--text-label);letter-spacing:.03em; +} +.settings-fold-title:before{ + content:"";width:4px;height:16px;flex-shrink:0; + background:linear-gradient(180deg,var(--accent),var(--accent-2)); + border-radius:2px;box-shadow:0 0 8px var(--card-glow); +} +.settings-fold-chevron{flex-shrink:0;font-size:.72rem;color:var(--text-muted);transition:transform .2s ease} +.settings-fold.is-collapsed .settings-fold-chevron{transform:rotate(-90deg)} +.settings-fold-body{padding:0 1rem 1rem;flex:1;display:flex;flex-direction:column} +.settings-fold.is-collapsed .settings-fold-body{display:none} +.settings-admin-row .settings-fold-head{padding:.75rem .85rem .6rem} +.settings-admin-row .settings-fold-title{font-size:.95rem} +.settings-admin-row .settings-fold-body{padding:0 .85rem .85rem} @media(max-width:900px){ + .settings-admin-row{grid-template-columns:1fr} .settings-password-form{grid-template-columns:1fr} .settings-ctp-cards-row{grid-template-columns:1fr} .settings-ctp-grid{grid-template-columns:1fr} @@ -77,11 +120,21 @@ {% endblock %} {% block content %} +{% macro settings_card(key, title, extra_class='') %} + +{% endmacro %}
-
-

导航显示

+ {% call settings_card('nav', '导航显示') %}

关闭后顶栏隐藏对应入口,直接访问 URL 也会跳转回下单监控。

@@ -95,10 +148,9 @@
-
+ {% endcall %} -
-

交易模式

+ {% call settings_card('trading', '交易模式') %}
@@ -143,12 +195,10 @@ 挂单超时:限价开仓未成交时,超过设定分钟数自动向柜台撤单(1~60 分钟)。CTP 账号与前置在下方「CTP 连接」中配置。

-
+ {% endcall %}
-
-

CTP 连接

-
+{% call settings_card('ctp', 'CTP 连接', 'settings-ctp-wrap') %}

投资者代码、密码、前置地址在此维护(优先于 .env)。保存后将自动断开并用新地址重连 CTP。 {% if ctp_status.connected %} @@ -164,8 +214,8 @@

-
-
-
-
-
+{% endcall %}
-
-

行情说明

+ {% call settings_card('quote', '行情说明') %}

当前行情源:{{ quote_label }}
@@ -290,10 +338,9 @@ 合约代码按同花顺格式(如 ag2608、IF2606)。

-
+ {% endcall %} -
-

企业微信推送

+ {% call settings_card('wechat', '企业微信推送') %}
@@ -303,19 +350,19 @@

在企业微信群添加机器人后,粘贴 Webhook 地址保存。

-
+ {% endcall %}
-
-

数据备份与恢复

+
+ {% call settings_card('backup', '数据备份与恢复', 'settings-compact-card') %}

自动备份目录:{{ backup_dir }} {% if backup_last_at %} · 上次备份 {{ backup_last_at.replace('T', ' ') }}{% else %} · 尚未备份{% endif %} {% if backup_running %} · 备份进行中…{% endif %}

-
+ -
+
-

备份包含 futures.dbuploads/,压缩包可在其他服务器恢复。默认恢复目录 {{ backup_restore_dir }}

+

备份含 futures.dbuploads/,默认恢复至 {{ backup_restore_dir }}

{% if backup_items %} @@ -351,33 +398,29 @@ - + {% endfor %}
{{ item.name }} {{ item.size_mb }} MB{{ item.mtime.replace('T', ' ') }}{{ item.mtime.replace('T', ' ')[:16] }} 下载
{% else %} -

暂无备份文件,可点击「立即备份」生成第一份。

+

暂无备份,可点「立即备份」。

{% endif %}
备份恢复说明 -
    -
  1. 下载上方 .tar.gz 到目标服务器(如 /root/)。
  2. -
  3. 解压:tar -xzf qihuo_backup_YYYYMMDD_HHMMSS.tar.gz
  4. -
  5. 进入目录执行:chmod +x restore.sh && ./restore.sh(默认恢复到 {{ backup_restore_dir }})。
  6. +
      +
    1. 下载 .tar.gz 到目标服务器(如 /root/)。
    2. +
    3. 解压:tar -xzf qihuo_backup_*.tar.gz
    4. +
    5. 执行:chmod +x restore.sh && ./restore.sh
    6. 指定目录:RESTORE_DIR=/opt/qihuo ./restore.sh
    7. -
    8. 在新服务器部署 qihuo 代码与虚拟环境,配置 .envpm2 restart qihuo
    9. -
    10. 恢复前请停止 qihuo,避免覆盖正在使用的数据库。
    11. +
    12. 部署代码、配置 .env 后重启服务。
    -

    完整说明见项目文档 docs/BACKUP.md;压缩包内亦含 RESTORE.md

-
+ {% endcall %} -
-
-

修改密码

+ {% call settings_card('password', '修改密码', 'settings-compact-card') %}
@@ -400,17 +443,18 @@
-
+ {% endcall %} +
-
-

使用提示

+
+ {% call settings_card('tips', '使用提示') %}
  • 下单监控:连接 CTP 后下单、看持仓与可开仓品种
  • 策略交易:趋势回调自动补仓;顺势加仓需先开仓
  • 手续费:默认 CTP 柜台费率,连接后点同步
  • 手机端:浏览器菜单可「添加到主屏幕」安装 App
-
+ {% endcall %}
@@ -430,6 +474,43 @@ if (sel) 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) { + 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; @@ -470,6 +551,8 @@ var ctpForm = document.getElementById('ctp-settings-form'); if (ctpForm) { 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');