Collapse field hints into a default-folded 规则说明 on strategy and runtime settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-26 21:52:49 +08:00
parent ca66c494e7
commit aca9d80e64
2 changed files with 159 additions and 41 deletions
+105 -41
View File
@@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from "react"; import { FormEvent, ReactNode, useEffect, useState } from "react";
import { import {
changeCredentials, changeCredentials,
getUsername, getUsername,
@@ -15,9 +15,38 @@ type StratSub =
| "exit" | "exit"
| "pace"; | "pace";
function RulesFold({
open,
onToggle,
children,
}: {
open: boolean;
onToggle: () => void;
children: ReactNode;
}) {
return (
<div className={`settings-rules ${open ? "open" : ""}`}>
<button
type="button"
className="settings-rules-toggle"
aria-expanded={open}
onClick={onToggle}
>
<span>规则说明</span>
<span className="settings-rules-chevron" aria-hidden>
{open ? "▾" : "▸"}
</span>
</button>
{open ? <div className="settings-rules-body">{children}</div> : null}
</div>
);
}
export default function SettingsPage() { export default function SettingsPage() {
const [tab, setTab] = useState<Tab>("strategy"); const [tab, setTab] = useState<Tab>("strategy");
const [stratSub, setStratSub] = useState<StratSub>("position"); const [stratSub, setStratSub] = useState<StratSub>("position");
const [stratRulesOpen, setStratRulesOpen] = useState(false);
const [runtimeRulesOpen, setRuntimeRulesOpen] = useState(false);
const [newUsername, setNewUsername] = useState(getUsername() || "admin"); const [newUsername, setNewUsername] = useState(getUsername() || "admin");
const [currentPassword, setCurrentPassword] = useState(""); const [currentPassword, setCurrentPassword] = useState("");
@@ -247,11 +276,6 @@ export default function SettingsPage() {
{tab === "strategy" ? ( {tab === "strategy" ? (
<div className="card settings-card"> <div className="card settings-card">
<p className="settings-lead">
{(runtime?.mode ?? mode) === "LIVE"
? "当前 LIVE:仓位/选约/出场影响真下单;模拟资金已隐藏。"
: "当前 SIM:本地撮合;可改模拟资金与交易所行情源。"}
</p>
{stratOk ? <div className="settings-ok">{stratOk}</div> : null} {stratOk ? <div className="settings-ok">{stratOk}</div> : null}
{err && tab === "strategy" ? <div className="err">{err}</div> : null} {err && tab === "strategy" ? <div className="err">{err}</div> : null}
@@ -301,9 +325,6 @@ export default function SettingsPage() {
币安(USDT 永续 + 欧洲期权) 币安(USDT 永续 + 欧洲期权)
</option> </option>
</select> </select>
<p className="settings-hint">
有持仓时不可切换。切换后套用该所合约并重连行情。
</p>
</div> </div>
{(runtime?.mode ?? mode) !== "LIVE" ? ( {(runtime?.mode ?? mode) !== "LIVE" ? (
<div className="field"> <div className="field">
@@ -319,9 +340,6 @@ export default function SettingsPage() {
setInitialEquity(Number(e.target.value)) setInitialEquity(Number(e.target.value))
} }
/> />
<p className="settings-hint">
仅 SIM 显示。保存后重置权益(须无持仓)。默认 10000。
</p>
</div> </div>
) : null} ) : null}
<div className="field"> <div className="field">
@@ -405,9 +423,6 @@ export default function SettingsPage() {
<option value="0">关闭(默认)</option> <option value="0">关闭(默认)</option>
<option value="1">开启</option> <option value="1">开启</option>
</select> </select>
<p className="settings-hint">
关闭时不限制 |ATM − 现价|;开启后超过下方点数则不开仓。
</p>
</div> </div>
<div className="field"> <div className="field">
<label htmlFor="atmoff">开仓 ATM 最大偏差(点)</label> <label htmlFor="atmoff">开仓 ATM 最大偏差(点)</label>
@@ -421,9 +436,6 @@ export default function SettingsPage() {
value={maxAtmOff} value={maxAtmOff}
onChange={(e) => setMaxAtmOff(Number(e.target.value))} onChange={(e) => setMaxAtmOff(Number(e.target.value))}
/> />
<p className="settings-hint">
仅开关开启时生效。币安行权价较粗时可能长时间无开仓机会。
</p>
</div> </div>
</div> </div>
</section> </section>
@@ -491,9 +503,6 @@ export default function SettingsPage() {
value={closeDevPct} value={closeDevPct}
onChange={(e) => setCloseDevPct(Number(e.target.value))} onChange={(e) => setCloseDevPct(Number(e.target.value))}
/> />
<p className="settings-hint">
主要用于 SIM 流动性闸门;LIVE 以交易所能否成交为准。
</p>
</div> </div>
</div> </div>
</section> </section>
@@ -530,9 +539,6 @@ export default function SettingsPage() {
setOrderInterval(Number(e.target.value)) setOrderInterval(Number(e.target.value))
} }
/> />
<p className="settings-hint">
LIVE 私有下单/查单间隔,默认 1s。范围 0.2–30。
</p>
</div> </div>
<div className="field"> <div className="field">
<label htmlFor="skipWe">周六日跳过开仓(上海时区)</label> <label htmlFor="skipWe">周六日跳过开仓(上海时区)</label>
@@ -561,6 +567,53 @@ export default function SettingsPage() {
</section> </section>
) : null} ) : null}
<RulesFold
open={stratRulesOpen}
onToggle={() => setStratRulesOpen((v) => !v)}
>
<ul className="settings-rules-list">
<li>
{(runtime?.mode ?? mode) === "LIVE"
? "当前 LIVE:仓位/选约/出场影响真下单;模拟资金已隐藏。"
: "当前 SIM:本地撮合;可改模拟资金与交易所行情源。"}
</li>
{stratSub === "position" ? (
<>
<li>交易所:有持仓时不可切换。切换后套用该所合约并重连行情。</li>
{(runtime?.mode ?? mode) !== "LIVE" ? (
<li>
模拟资金:仅 SIM 显示。保存后重置权益(须无持仓)。默认
10000。
</li>
) : null}
</>
) : null}
{stratSub === "select" ? (
<>
<li>
开仓 ATM 偏差限制:关闭时不限制 |ATM −
现价|;开启后超过下方点数则不开仓。
</li>
<li>
开仓 ATM 最大偏差:仅开关开启时生效。币安行权价较粗时可能长时间无开仓机会。
</li>
</>
) : null}
{stratSub === "exit" ? (
<li>
平仓买一/标记最大偏差:主要用于 SIM
流动性闸门;LIVE 以交易所能否成交为准。
</li>
) : null}
{stratSub === "pace" ? (
<li>
实盘下单最小间隔:LIVE 私有下单/查单间隔,默认 1s。范围
0.2–30。
</li>
) : null}
</ul>
</RulesFold>
<div className="settings-actions"> <div className="settings-actions">
<button className="btn" type="submit"> <button className="btn" type="submit">
保存策略 保存策略
@@ -572,10 +625,6 @@ export default function SettingsPage() {
{tab === "runtime" ? ( {tab === "runtime" ? (
<div className="card settings-card"> <div className="card settings-card">
<p className="settings-lead">
SIM = 本地撮合;LIVE = 当前所选交易所真下单(OKX / 币安均已接入)。密钥只写入服务器{" "}
<span className="mono">.env</span>,接口不回传明文。OKX 期权常需账户有 USDC;币安多为 USDT。
</p>
{runtimeOk ? <div className="settings-ok">{runtimeOk}</div> : null} {runtimeOk ? <div className="settings-ok">{runtimeOk}</div> : null}
{err && tab === "runtime" ? <div className="err">{err}</div> : null} {err && tab === "runtime" ? <div className="err">{err}</div> : null}
<form onSubmit={onSaveRuntime}> <form onSubmit={onSaveRuntime}>
@@ -595,15 +644,6 @@ export default function SettingsPage() {
<option value="SIM">SIM 本地模拟</option> <option value="SIM">SIM 本地模拟</option>
<option value="LIVE">LIVE 实盘</option> <option value="LIVE">LIVE 实盘</option>
</select> </select>
<p className="settings-hint">
当前:{runtime?.mode || "—"} · 交易所 {runtime?.exchange || "—"} ·{" "}
{runtime?.mode === "LIVE"
? runtime.live_ready
? "LIVE 就绪"
: `未就绪(${runtime.live_ready_reason})`
: "SIM"}
。有持仓时不可切换模式。
</p>
</div> </div>
</div> </div>
</section> </section>
@@ -697,13 +737,37 @@ export default function SettingsPage() {
onChange={(e) => setBnSecret(e.target.value)} onChange={(e) => setBnSecret(e.target.value)}
/> />
</div> </div>
<p className="settings-hint">
留空保存=不覆盖已有密钥。交易所选币安且 LIVE 时用此密钥真下单(无
Passphrase)。
</p>
</div> </div>
</section> </section>
<RulesFold
open={runtimeRulesOpen}
onToggle={() => setRuntimeRulesOpen((v) => !v)}
>
<ul className="settings-rules-list">
<li>
SIM = 本地撮合;LIVE =
当前所选交易所真下单(OKX / 币安均已接入)。密钥只写入服务器{" "}
<span className="mono">.env</span>
,接口不回传明文。OKX 期权常需账户有 USDC;币安多为 USDT。
</li>
<li>
当前:{runtime?.mode || "—"} · 交易所{" "}
{runtime?.exchange || "—"} ·{" "}
{runtime?.mode === "LIVE"
? runtime.live_ready
? "LIVE 就绪"
: `未就绪(${runtime.live_ready_reason})`
: "SIM"}
。有持仓时不可切换模式。
</li>
<li>
密钥留空保存 = 不覆盖已有配置。交易所选币安且 LIVE
时用币安密钥真下单(无 Passphrase)。
</li>
</ul>
</RulesFold>
<div className="settings-actions"> <div className="settings-actions">
<button className="btn" type="submit" disabled={loading}> <button className="btn" type="submit" disabled={loading}>
{loading ? "保存中…" : "保存模式与密钥"} {loading ? "保存中…" : "保存模式与密钥"}
+54
View File
@@ -472,6 +472,60 @@ input {
line-height: 1.4; line-height: 1.4;
} }
.settings-rules {
margin: 4px 0 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--input);
}
.settings-rules-toggle {
appearance: none;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
border: 0;
background: transparent;
color: var(--muted);
padding: 10px 12px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: left;
}
.settings-rules-toggle:hover {
color: var(--text);
}
.settings-rules.open .settings-rules-toggle {
color: var(--text);
border-bottom: 1px solid var(--line);
}
.settings-rules-chevron {
color: var(--muted);
font-size: 12px;
}
.settings-rules-body {
padding: 10px 12px 12px;
}
.settings-rules-list {
margin: 0;
padding-left: 18px;
color: var(--muted);
font-size: 12px;
line-height: 1.55;
}
.settings-rules-list li + li {
margin-top: 6px;
}
.settings-actions { .settings-actions {
margin-top: 8px; margin-top: 8px;
display: flex; display: flex;