Show option expiry countdown and widen settings for ultrawide/tablet.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -507,6 +507,14 @@ class Matcher:
|
||||
)
|
||||
strike = float(g["strike"]) if g and g["strike"] is not None else None
|
||||
expiry_ymd = str(g["expiry_ymd"]) if g and g["expiry_ymd"] else None
|
||||
expiry_ms = None
|
||||
if expiry_ymd and len(expiry_ymd) == 6:
|
||||
try:
|
||||
from ..exchange.okx.parse import expiry_ms_from_ymd
|
||||
|
||||
expiry_ms = expiry_ms_from_ymd(expiry_ymd)
|
||||
except Exception:
|
||||
expiry_ms = None
|
||||
perp_inst_id = (
|
||||
str(g["perp_inst_id"])
|
||||
if g and g["perp_inst_id"]
|
||||
@@ -532,6 +540,7 @@ class Matcher:
|
||||
"option_mark_px": float(opt_mark) if opt_mark is not None else None,
|
||||
"strike": strike,
|
||||
"expiry_ymd": expiry_ymd,
|
||||
"expiry_ms": expiry_ms,
|
||||
"perp_upl": perp_upl,
|
||||
"option_upl": option_upl,
|
||||
"est_close_fees": est_close_fees,
|
||||
|
||||
@@ -149,6 +149,7 @@ export type PlanState = {
|
||||
option_mark_px?: number | null;
|
||||
strike?: number | null;
|
||||
expiry_ymd?: string | null;
|
||||
expiry_ms?: number | null;
|
||||
perp_upl?: number;
|
||||
option_upl?: number;
|
||||
est_close_fees?: number;
|
||||
|
||||
@@ -11,6 +11,28 @@ function pnlClass(n: number | null | undefined) {
|
||||
return n > 0 ? "pos-pnl-profit" : "pos-pnl-loss";
|
||||
}
|
||||
|
||||
/** OKX 期权到期:YYMMDD 当日 08:00 UTC(上海 16:00) */
|
||||
function expiryMsFromYmd(ymd: string | null | undefined): number | null {
|
||||
if (!ymd || ymd.length !== 6) return null;
|
||||
const yy = 2000 + Number(ymd.slice(0, 2));
|
||||
const mm = Number(ymd.slice(2, 4));
|
||||
const dd = Number(ymd.slice(4, 6));
|
||||
if (![yy, mm, dd].every((x) => Number.isFinite(x))) return null;
|
||||
return Date.UTC(yy, mm - 1, dd, 8, 0, 0);
|
||||
}
|
||||
|
||||
function formatCountdown(msLeft: number): string {
|
||||
if (msLeft <= 0) return "已到期";
|
||||
const s = Math.floor(msLeft / 1000);
|
||||
const d = Math.floor(s / 86400);
|
||||
const h = Math.floor((s % 86400) / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const sec = s % 60;
|
||||
if (d > 0) return `${d}天 ${h}时 ${String(m).padStart(2, "0")}分`;
|
||||
if (h > 0) return `${h}时 ${String(m).padStart(2, "0")}分 ${String(sec).padStart(2, "0")}秒`;
|
||||
return `${m}分 ${String(sec).padStart(2, "0")}秒`;
|
||||
}
|
||||
|
||||
const PHASE_ZH: Record<string, string> = {
|
||||
idle: "空闲",
|
||||
wait_signal: "等待信号",
|
||||
@@ -29,6 +51,7 @@ export default function PlanPage() {
|
||||
const [plan, setPlan] = useState<PlanState | null>(null);
|
||||
const [err, setErr] = useState("");
|
||||
const [busy, setBusy] = useState("");
|
||||
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
@@ -50,6 +73,11 @@ export default function PlanPage() {
|
||||
return () => window.clearInterval(t);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const t = window.setInterval(() => setNowMs(Date.now()), 1000);
|
||||
return () => window.clearInterval(t);
|
||||
}, []);
|
||||
|
||||
async function act(path: string, label: string) {
|
||||
setBusy(label);
|
||||
setErr("");
|
||||
@@ -84,6 +112,12 @@ export default function PlanPage() {
|
||||
? `权利金×${fmt(plan?.premium_exit_multiple ?? 1, 2)}`
|
||||
: `固定 ${fmt(plan?.net_profit_target ?? 15)} U`;
|
||||
const phaseLabel = PHASE_ZH[plan?.phase || ""] || plan?.phase || "—";
|
||||
const expiryMs =
|
||||
pos?.expiry_ms ?? expiryMsFromYmd(pos?.expiry_ymd) ?? null;
|
||||
const countdown =
|
||||
open && expiryMs != null ? formatCountdown(expiryMs - nowMs) : "—";
|
||||
const countdownUrgent =
|
||||
open && expiryMs != null && expiryMs - nowMs > 0 && expiryMs - nowMs < 6 * 3600_000;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -290,6 +324,14 @@ export default function PlanPage() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="pos-grid">
|
||||
<div className="pos-cell pos-cell-wide">
|
||||
<span className="pos-label">到期倒计时</span>
|
||||
<span
|
||||
className={`pos-value mono ${countdownUrgent ? "pos-countdown-urgent" : ""}`}
|
||||
>
|
||||
{countdown}
|
||||
</span>
|
||||
</div>
|
||||
<div className="pos-cell">
|
||||
<span className="pos-label">开仓均价</span>
|
||||
<span className="pos-value mono">{fmt(pos?.option_entry_px)}</span>
|
||||
|
||||
+209
-188
@@ -112,7 +112,7 @@ export default function SettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 560 }}>
|
||||
<div className="settings-page">
|
||||
<h2 style={{ marginTop: 0 }}>系统设置</h2>
|
||||
<div className="tabs">
|
||||
<button
|
||||
@@ -132,207 +132,228 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
{tab === "strategy" ? (
|
||||
<div className="card">
|
||||
<p style={{ color: "var(--muted)", marginTop: 0 }}>
|
||||
<div className="card settings-card">
|
||||
<p className="settings-lead">
|
||||
无开仓时间窗;期权按剩余时长选到期 → 平值 → 校验杠杆;出场可选固定金额或权利金倍数。
|
||||
</p>
|
||||
{stratOk ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{stratOk}</div> : null}
|
||||
{stratOk ? <div className="settings-ok">{stratOk}</div> : null}
|
||||
{err && tab === "strategy" ? <div className="err">{err}</div> : null}
|
||||
<form onSubmit={onSaveStrategy}>
|
||||
<h3 style={{ margin: "0 0 10px", fontSize: 14, color: "var(--muted)" }}>资金</h3>
|
||||
<div className="field">
|
||||
<label htmlFor="lev">永续杠杆倍数</label>
|
||||
<input
|
||||
id="lev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={leverage}
|
||||
onChange={(e) => setLeverage(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<section className="settings-section">
|
||||
<h3>资金</h3>
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="lev">永续杠杆倍数</label>
|
||||
<input
|
||||
id="lev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={leverage}
|
||||
onChange={(e) => setLeverage(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="perp">永续 ETH 数量</label>
|
||||
<input
|
||||
id="perp"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={perpQty}
|
||||
onChange={(e) => setPerpQty(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="opt">期权 ETH 数量</label>
|
||||
<input
|
||||
id="opt"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={optQty}
|
||||
onChange={(e) => setOptQty(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h3 style={{ margin: "18px 0 10px", fontSize: 14, color: "var(--muted)" }}>策略</h3>
|
||||
<div className="field">
|
||||
<label htmlFor="hours">期权最小剩余小时</label>
|
||||
<input
|
||||
id="hours"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={minHours}
|
||||
onChange={(e) => setMinHours(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="olev">期权杠杆下限(现价÷卖一权利金)</label>
|
||||
<input
|
||||
id="olev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={minOptLev}
|
||||
onChange={(e) => setMinOptLev(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="exitMode">出场规则</label>
|
||||
<select
|
||||
id="exitMode"
|
||||
className="mono"
|
||||
value={exitMode}
|
||||
onChange={(e) =>
|
||||
setExitMode(
|
||||
e.target.value === "premium_multiple"
|
||||
? "premium_multiple"
|
||||
: "fixed_usdt",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="fixed_usdt">固定净盈利(USDT)</option>
|
||||
<option value="premium_multiple">权利金倍数</option>
|
||||
</select>
|
||||
</div>
|
||||
{exitMode === "fixed_usdt" ? (
|
||||
<div className="field">
|
||||
<label htmlFor="netTarget">净盈利出场目标(USDT)</label>
|
||||
<input
|
||||
id="netTarget"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
value={netTarget}
|
||||
onChange={(e) => setNetTarget(Number(e.target.value))}
|
||||
/>
|
||||
<section className="settings-section">
|
||||
<h3>选约</h3>
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="hours">期权最小剩余小时</label>
|
||||
<input
|
||||
id="hours"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={minHours}
|
||||
onChange={(e) => setMinHours(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="olev">期权杠杆下限(现价÷卖一权利金)</label>
|
||||
<input
|
||||
id="olev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={minOptLev}
|
||||
onChange={(e) => setMinOptLev(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="field">
|
||||
<label htmlFor="premMult">权利金倍数(1 = 一倍权利金)</label>
|
||||
<input
|
||||
id="premMult"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
value={premMult}
|
||||
onChange={(e) => setPremMult(Number(e.target.value))}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>出场与风控</h3>
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="exitMode">出场规则</label>
|
||||
<select
|
||||
id="exitMode"
|
||||
className="mono"
|
||||
value={exitMode}
|
||||
onChange={(e) =>
|
||||
setExitMode(
|
||||
e.target.value === "premium_multiple"
|
||||
? "premium_multiple"
|
||||
: "fixed_usdt",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="fixed_usdt">固定净盈利(USDT)</option>
|
||||
<option value="premium_multiple">权利金倍数</option>
|
||||
</select>
|
||||
</div>
|
||||
{exitMode === "fixed_usdt" ? (
|
||||
<div className="field">
|
||||
<label htmlFor="netTarget">净盈利出场目标(USDT)</label>
|
||||
<input
|
||||
id="netTarget"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
value={netTarget}
|
||||
onChange={(e) => setNetTarget(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="field">
|
||||
<label htmlFor="premMult">权利金倍数(1 = 一倍权利金)</label>
|
||||
<input
|
||||
id="premMult"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0.1"
|
||||
value={premMult}
|
||||
onChange={(e) => setPremMult(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="field">
|
||||
<label htmlFor="closeDev">平仓买一/标记最大偏差(%)</label>
|
||||
<input
|
||||
id="closeDev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={closeDevPct}
|
||||
onChange={(e) => setCloseDevPct(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="rest">轮间休息秒数</label>
|
||||
<input
|
||||
id="rest"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
value={rest}
|
||||
onChange={(e) => setRest(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="fee">手续费率</label>
|
||||
<input
|
||||
id="fee"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={fee}
|
||||
onChange={(e) => setFee(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="field">
|
||||
<label htmlFor="closeDev">平仓买一/标记最大偏差(%)</label>
|
||||
<input
|
||||
id="closeDev"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
value={closeDevPct}
|
||||
onChange={(e) => setCloseDevPct(Number(e.target.value))}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<div className="settings-actions">
|
||||
<button className="btn" type="submit">
|
||||
保存策略
|
||||
</button>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="perp">永续 ETH 数量</label>
|
||||
<input
|
||||
id="perp"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={perpQty}
|
||||
onChange={(e) => setPerpQty(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="opt">期权 ETH 数量</label>
|
||||
<input
|
||||
id="opt"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={optQty}
|
||||
onChange={(e) => setOptQty(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="rest">轮间休息秒数</label>
|
||||
<input
|
||||
id="rest"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="1"
|
||||
value={rest}
|
||||
onChange={(e) => setRest(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="fee">手续费率</label>
|
||||
<input
|
||||
id="fee"
|
||||
className="mono"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
value={fee}
|
||||
onChange={(e) => setFee(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="submit">
|
||||
保存策略
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<div className="card">
|
||||
<div className="card settings-card settings-card-narrow">
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
{ok ? <div style={{ color: "var(--up)", marginBottom: 12 }}>{ok}</div> : null}
|
||||
<form onSubmit={onSaveCreds}>
|
||||
<div className="field">
|
||||
<label htmlFor="user">新用户名</label>
|
||||
<input
|
||||
id="user"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{ok ? <div className="settings-ok">{ok}</div> : null}
|
||||
<form onSubmit={onSaveCreds} className="settings-account-form">
|
||||
<div className="settings-fields">
|
||||
<div className="field">
|
||||
<label htmlFor="user">新用户名</label>
|
||||
<input
|
||||
id="user"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cur">当前密码</label>
|
||||
<input
|
||||
id="cur"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="np">新密码</label>
|
||||
<input
|
||||
id="np"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cp">确认新密码</label>
|
||||
<input
|
||||
id="cp"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cur">当前密码</label>
|
||||
<input
|
||||
id="cur"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<div className="settings-actions">
|
||||
<button className="btn" type="submit" disabled={loading}>
|
||||
{loading ? "保存中…" : "保存账号"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="np">新密码</label>
|
||||
<input
|
||||
id="np"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="cp">确认新密码</label>
|
||||
<input
|
||||
id="cp"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button className="btn" type="submit" disabled={loading}>
|
||||
{loading ? "保存中…" : "保存账号"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+145
-2
@@ -145,12 +145,49 @@ input {
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 16px;
|
||||
max-width: 1200px;
|
||||
padding: 16px 20px 28px;
|
||||
max-width: min(1680px, 100%);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 1800px) {
|
||||
.page {
|
||||
max-width: 1760px;
|
||||
padding-left: 28px;
|
||||
padding-right: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.page {
|
||||
padding: 12px 14px 24px;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.topnav-side.left,
|
||||
.topnav-side.right {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.topnav-center {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.topnav a {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--line);
|
||||
@@ -233,6 +270,8 @@ input {
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.field input:focus,
|
||||
@@ -240,6 +279,96 @@ input {
|
||||
border-color: rgba(240, 185, 11, 0.55);
|
||||
}
|
||||
|
||||
/* Settings: ultrawide + tablet */
|
||||
.settings-page {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-card-narrow {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.settings-lead {
|
||||
color: var(--muted);
|
||||
margin: 0 0 16px;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-ok {
|
||||
color: var(--up);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.settings-section:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.settings-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0 18px;
|
||||
}
|
||||
|
||||
.settings-fields .field {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.settings-fields {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.settings-page {
|
||||
max-width: 1320px;
|
||||
}
|
||||
|
||||
.settings-fields {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 699px) {
|
||||
.settings-fields .field {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.field input,
|
||||
.field select {
|
||||
min-height: 44px;
|
||||
font-size: 16px; /* 避免 iOS 自动缩放 */
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
@@ -385,12 +514,26 @@ input {
|
||||
gap: 12px 14px;
|
||||
}
|
||||
|
||||
.pos-cell-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.pos-countdown-urgent {
|
||||
color: #ffb020 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.pos-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
.pos-embed-grid {
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.pos-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user