feat: 系统设置 CTP 连接拆分为 SimNow/实盘可折叠卡片

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 17:09:56 +08:00
parent 259d9e812d
commit 480000e195
+165 -91
View File
@@ -16,11 +16,29 @@
.settings-tips li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;border-radius:50%;background:var(--accent)} .settings-tips li::before{content:"";position:absolute;left:0;top:.55em;width:5px;height:5px;border-radius:50%;background:var(--accent)}
.settings-ctp-grid{display:grid;grid-template-columns:1fr 1fr;gap:.65rem .75rem} .settings-ctp-grid{display:grid;grid-template-columns:1fr 1fr;gap:.65rem .75rem}
.settings-ctp-grid .field-full{grid-column:1/-1} .settings-ctp-grid .field-full{grid-column:1/-1}
.settings-ctp-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--border)} .settings-ctp-wrap .card-body{padding-top:0}
.settings-ctp-section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0} .settings-ctp-fold.card{
.settings-ctp-section h3{font-size:.9rem;margin:0 0 .65rem;color:var(--text-title)} margin-bottom:.75rem;padding:0;overflow:hidden;
border:1px solid var(--border);border-radius:8px;background:var(--card-inner);
}
.settings-ctp-fold.card:last-of-type{margin-bottom:0}
.settings-ctp-fold-head{
width:100%;display:flex;align-items:center;justify-content:space-between;gap:.75rem;
padding:.7rem 1rem;margin:0;border:none;background:transparent;cursor:pointer;
font-size:.92rem;font-weight:600;color:var(--text-title);text-align:left;
}
.settings-ctp-fold-head:hover{color:var(--accent)}
.settings-ctp-fold-title{display:flex;align-items:center;gap:.5rem}
.settings-ctp-fold-chevron{
flex-shrink:0;font-size:.72rem;color:var(--text-muted);
transition:transform .2s ease;
}
.settings-ctp-fold.is-collapsed .settings-ctp-fold-chevron{transform:rotate(-90deg)}
.settings-ctp-fold-body{padding:0 1rem .85rem}
.settings-ctp-fold.is-collapsed .settings-ctp-fold-body{display:none}
.settings-ctp-status{font-size:.82rem;color:var(--text-muted);margin-top:.75rem;line-height:1.5} .settings-ctp-status{font-size:.82rem;color:var(--text-muted);margin-top:.75rem;line-height:1.5}
@media(max-width:900px){ @media(max-width:900px){
.settings-password-form{grid-template-columns:1fr}
.settings-ctp-grid{grid-template-columns:1fr} .settings-ctp-grid{grid-template-columns:1fr}
} }
</style> </style>
@@ -90,10 +108,9 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card settings-ctp-wrap">
<h2>CTP 连接</h2> <h2>CTP 连接</h2>
<form action="{{ url_for('settings') }}" method="post"> <div class="card-body">
<input type="hidden" name="action" value="ctp">
<p class="hint" style="margin-bottom:.85rem"> <p class="hint" style="margin-bottom:.85rem">
投资者代码、密码、前置地址在此维护(优先于 <code>.env</code>)。保存后请在持仓监控页点击「重连 CTP」。 投资者代码、密码、前置地址在此维护(优先于 <code>.env</code>)。保存后请在持仓监控页点击「重连 CTP」。
{% if ctp_status.connected %} {% if ctp_status.connected %}
@@ -105,97 +122,117 @@
{% endif %} {% endif %}
</p> </p>
<div class="settings-ctp-section"> <form action="{{ url_for('settings') }}" method="post" id="ctp-settings-form">
<h3>SimNow 模拟盘</h3> <input type="hidden" name="action" value="ctp">
<div class="settings-ctp-grid">
<div class="field"> <div class="settings-ctp-fold card{% if trading_mode != 'simulation' %} is-collapsed{% endif %}" data-ctp-fold="simnow">
<label>投资者代码</label> <button type="button" class="settings-ctp-fold-head" aria-expanded="{{ 'true' if trading_mode == 'simulation' else 'false' }}">
<input name="simnow_user" value="{{ ctp_cfg.simnow_user }}" placeholder="非手机号"> <span class="settings-ctp-fold-title">
</div> SimNow 模拟盘
<div class="field"> {% if trading_mode == 'simulation' %}<span class="badge planned" style="font-size:.7rem">当前通道</span>{% endif %}
<label>交易密码</label> </span>
<input name="simnow_password" type="password" autocomplete="new-password" <span class="settings-ctp-fold-chevron" aria-hidden="true"></span>
placeholder="{% if ctp_cfg.simnow_password_set %}已设置,留空不修改{% else %}SimNow 密码{% endif %}"> </button>
</div> <div class="settings-ctp-fold-body">
<div class="field"> <div class="settings-ctp-grid">
<label>经纪商代码</label> <div class="field">
<input name="simnow_broker_id" value="{{ ctp_cfg.simnow_broker_id }}"> <label>投资者代码</label>
</div> <input name="simnow_user" value="{{ ctp_cfg.simnow_user }}" placeholder="非手机号">
<div class="field"> </div>
<label>柜台环境</label> <div class="field">
<select name="simnow_env"> <label>交易密码</label>
<option value="实盘" {% if ctp_cfg.simnow_env == '实盘' %}selected{% endif %}>实盘(看穿式,推荐)</option> <input name="simnow_password" type="password" autocomplete="new-password"
<option value="测试" {% if ctp_cfg.simnow_env == '测试' %}selected{% endif %}>测试</option> placeholder="{% if ctp_cfg.simnow_password_set %}已设置,留空不修改{% else %}SimNow 密码{% endif %}">
</select> </div>
</div> <div class="field">
<div class="field field-full"> <label>经纪商代码</label>
<label>交易前置</label> <input name="simnow_broker_id" value="{{ ctp_cfg.simnow_broker_id }}">
<input name="simnow_td_address" value="{{ ctp_cfg.simnow_td_address }}" placeholder="tcp://180.168.146.187:10201"> </div>
</div> <div class="field">
<div class="field field-full"> <label>柜台环境</label>
<label>行情前置</label> <select name="simnow_env">
<input name="simnow_md_address" value="{{ ctp_cfg.simnow_md_address }}" placeholder="tcp://180.168.146.187:10211"> <option value="实盘" {% if ctp_cfg.simnow_env == '实盘' %}selected{% endif %}>实盘(看穿式,推荐)</option>
</div> <option value="测试" {% if ctp_cfg.simnow_env == '测试' %}selected{% endif %}>测试</option>
<div class="field"> </select>
<label>AppID</label> </div>
<input name="simnow_app_id" value="{{ ctp_cfg.simnow_app_id }}"> <div class="field field-full">
</div> <label>交易前置</label>
<div class="field"> <input name="simnow_td_address" value="{{ ctp_cfg.simnow_td_address }}" placeholder="tcp://180.168.146.187:10201">
<label>授权编码</label> </div>
<input name="simnow_auth_code" value="{{ ctp_cfg.simnow_auth_code }}"> <div class="field field-full">
<label>行情前置</label>
<input name="simnow_md_address" value="{{ ctp_cfg.simnow_md_address }}" placeholder="tcp://180.168.146.187:10211">
</div>
<div class="field">
<label>AppID</label>
<input name="simnow_app_id" value="{{ ctp_cfg.simnow_app_id }}">
</div>
<div class="field">
<label>授权编码</label>
<input name="simnow_auth_code" value="{{ ctp_cfg.simnow_auth_code }}">
</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="settings-ctp-section"> <div class="settings-ctp-fold card{% if trading_mode != 'live' %} is-collapsed{% endif %}" data-ctp-fold="live">
<h3>期货公司实盘(后期)</h3> <button type="button" class="settings-ctp-fold-head" aria-expanded="{{ 'true' if trading_mode == 'live' else 'false' }}">
<div class="settings-ctp-grid"> <span class="settings-ctp-fold-title">
<div class="field"> 期货公司实盘
<label>投资者代码</label> {% if trading_mode == 'live' %}<span class="badge planned" style="font-size:.7rem">当前通道</span>{% endif %}
<input name="ctp_live_user" value="{{ ctp_cfg.ctp_live_user }}"> </span>
</div> <span class="settings-ctp-fold-chevron" aria-hidden="true"></span>
<div class="field"> </button>
<label>交易密码</label> <div class="settings-ctp-fold-body">
<input name="ctp_live_password" type="password" autocomplete="new-password" <div class="settings-ctp-grid">
placeholder="{% if ctp_cfg.ctp_live_password_set %}已设置,留空不修改{% else %}实盘密码{% endif %}"> <div class="field">
</div> <label>投资者代码</label>
<div class="field"> <input name="ctp_live_user" value="{{ ctp_cfg.ctp_live_user }}">
<label>经纪商代码</label> </div>
<input name="ctp_live_broker_id" value="{{ ctp_cfg.ctp_live_broker_id }}"> <div class="field">
</div> <label>交易密码</label>
<div class="field"> <input name="ctp_live_password" type="password" autocomplete="new-password"
<label>柜台环境</label> placeholder="{% if ctp_cfg.ctp_live_password_set %}已设置,留空不修改{% else %}实盘密码{% endif %}">
<select name="ctp_live_env"> </div>
<option value="实盘" {% if ctp_cfg.ctp_live_env == '实盘' %}selected{% endif %}>实盘</option> <div class="field">
<option value="测试" {% if ctp_cfg.ctp_live_env == '测试' %}selected{% endif %}>测试</option> <label>经纪商代码</label>
</select> <input name="ctp_live_broker_id" value="{{ ctp_cfg.ctp_live_broker_id }}">
</div> </div>
<div class="field field-full"> <div class="field">
<label>交易前置</label> <label>柜台环境</label>
<input name="ctp_live_td_address" value="{{ ctp_cfg.ctp_live_td_address }}" placeholder="tcp://..."> <select name="ctp_live_env">
</div> <option value="实盘" {% if ctp_cfg.ctp_live_env == '实盘' %}selected{% endif %}>实盘</option>
<div class="field field-full"> <option value="测试" {% if ctp_cfg.ctp_live_env == '测试' %}selected{% endif %}>测试</option>
<label>行情前置</label> </select>
<input name="ctp_live_md_address" value="{{ ctp_cfg.ctp_live_md_address }}" placeholder="tcp://..."> </div>
</div> <div class="field field-full">
<div class="field"> <label>交易前置</label>
<label>AppID</label> <input name="ctp_live_td_address" value="{{ ctp_cfg.ctp_live_td_address }}" placeholder="tcp://...">
<input name="ctp_live_app_id" value="{{ ctp_cfg.ctp_live_app_id }}"> </div>
</div> <div class="field field-full">
<div class="field"> <label>行情前置</label>
<label>授权编码</label> <input name="ctp_live_md_address" value="{{ ctp_cfg.ctp_live_md_address }}" placeholder="tcp://...">
<input name="ctp_live_auth_code" value="{{ ctp_cfg.ctp_live_auth_code }}"> </div>
<div class="field">
<label>AppID</label>
<input name="ctp_live_app_id" value="{{ ctp_cfg.ctp_live_app_id }}">
</div>
<div class="field">
<label>授权编码</label>
<input name="ctp_live_auth_code" value="{{ ctp_cfg.ctp_live_auth_code }}">
</div>
</div>
</div> </div>
</div> </div>
</div>
<button type="submit" class="btn-primary">保存 CTP 配置</button> <button type="submit" class="btn-primary">保存 CTP 配置</button>
<p class="settings-ctp-status"> <p class="settings-ctp-status">
官方第一套:<code>180.168.146.187:10201/10211</code> 官方第一套:<code>180.168.146.187:10201/10211</code>
7×24<code>182.254.243.31:40001/40011</code>(新账号可能需满 3 个交易日)。 7×24<code>182.254.243.31:40001/40011</code>(新账号可能需满 3 个交易日)。
详见 <code>docs/SIMNOW.md</code> 详见 <code>docs/SIMNOW.md</code>
</p> </p>
</form> </form>
</div>
</div> </div>
<div class="split-grid"> <div class="split-grid">
@@ -278,6 +315,43 @@
} }
if (sel) sel.addEventListener('change', syncSizingFields); if (sel) sel.addEventListener('change', syncSizingFields);
syncSizingFields(); syncSizingFields();
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) {
btn.addEventListener('click', function () {
var panel = btn.closest('[data-ctp-fold]');
if (!panel) return;
setCtpFold(panel, !panel.classList.contains('is-collapsed'));
saveCtpFoldState();
});
});
loadCtpFoldState();
})(); })();
</script> </script>
{% endblock %} {% endblock %}