diff --git a/docs/更新说明.md b/docs/更新说明.md index 86104ee..5b4b648 100644 --- a/docs/更新说明.md +++ b/docs/更新说明.md @@ -5,6 +5,17 @@ --- +## 2026-07-29 — 桌面端:备份规则说明、交易记录表格翻页 + +### 变更 + +1. 备份恢复:说明文字收入「规则说明」;服务器备份列表一行一项,桌面最多显示约 5 行(可滚动)。 +2. 运行模式:企业微信说明收入「规则说明」。 +3. 交易记录(仅电脑端):表格列(序号/组名/方向/持仓状态/开平时间/时长/盈亏/平仓方式)+ 每页 10 行翻页;手机端列表不变。 +4. 手机端继续隐藏顶部资金条。 + +--- + ## 2026-07-24 — 策略设置:固定方向开关 ### 变更 diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index f44f59e..d4a28b6 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -51,6 +51,7 @@ export default function SettingsPage() { const [stratSub, setStratSub] = useState("position"); const [stratRulesOpen, setStratRulesOpen] = useState(false); const [runtimeRulesOpen, setRuntimeRulesOpen] = useState(false); + const [backupRulesOpen, setBackupRulesOpen] = useState(false); const [newUsername, setNewUsername] = useState(getUsername() || "admin"); const [currentPassword, setCurrentPassword] = useState(""); @@ -1000,18 +1001,6 @@ export default function SettingsPage() {

企业微信通知

-

- 群机器人 Webhook。推送 Markdown,带标识(OPEN/CLOSE/START/PAUSE/FAULT)。 - 当前标签: - - {" "} - {wecomMeta?.venue_label || - (runtime?.mode === "LIVE" - ? `实盘·${(runtime.exchange || "okx").toUpperCase()}` - : "模拟盘")} - - 。开仓/平仓/启动/暂停/故障均推送;模拟盘标题为「模拟盘」,实盘为「实盘·交易所」。 -

@@ -1082,6 +1071,17 @@ export default function SettingsPage() { 密钥留空保存 = 不覆盖已有配置。交易所选币安且 LIVE 时用币安密钥真下单(无 Passphrase)。 +
  • + 企业微信:群机器人 Webhook,推送 Markdown,带标识(OPEN/CLOSE/START/PAUSE/FAULT)。 + 当前标签: + + {wecomMeta?.venue_label || + (runtime?.mode === "LIVE" + ? `实盘·${(runtime.exchange || "okx").toUpperCase()}` + : "模拟盘")} + + 。开仓/平仓/启动/暂停/故障均推送;模拟盘标题为「模拟盘」,实盘为「实盘·交易所」。 +
  • 企业微信:在群里添加「自定义机器人」复制 Webhook。留空保存不覆盖已有 URL。故障同类消息 5 分钟内去重。 @@ -1336,14 +1336,6 @@ export default function SettingsPage() {

    自动备份

    -

    - 备份内容:SQLite 数据库 +{" "} - .env(含登录与交易所密钥)。服务器目录: - - {" "} - {backup?.backup_dir || "/root/eth_hedge_backups"} - -

    @@ -1401,21 +1393,12 @@ export default function SettingsPage() {

    服务器备份列表

    {backup?.items && backup.items.length > 0 ? ( -
    +
    {backup.items.map((it) => ( -
    - +
    + {it.name} - + {" "} · {(it.size_bytes / 1024).toFixed(1)} KB ·{" "} {new Date(it.mtime_ms).toLocaleString()} @@ -1439,13 +1422,6 @@ export default function SettingsPage() {

    上传恢复(新服务器迁移)

    -

    - 在新机器部署后,上传旧机下载的 zip,将覆盖数据库与{" "} - .env - 。须无持仓;确认串填写{" "} - RESTORE - 。恢复后服务自动重启。 -

    @@ -1480,6 +1456,30 @@ export default function SettingsPage() {
    + + setBackupRulesOpen((v) => !v)} + > +
      +
    • + 备份内容:SQLite 数据库 +{" "} + .env + (含登录与交易所密钥)。服务器目录: + + {backup?.backup_dir || "/root/eth_hedge_backups"} + + 。 +
    • +
    • + 上传恢复:在新机器部署后,上传旧机下载的 zip,将覆盖数据库与{" "} + .env + 。须无持仓;确认串填写{" "} + RESTORE + 。恢复后服务自动重启。 +
    • +
    +
    ) : null}
    diff --git a/frontend/src/pages/Trades.tsx b/frontend/src/pages/Trades.tsx index c41b6c8..cd75328 100644 --- a/frontend/src/pages/Trades.tsx +++ b/frontend/src/pages/Trades.tsx @@ -1,4 +1,4 @@ -import { useEffect, useId, useState } from "react"; +import { useEffect, useId, useMemo, useState } from "react"; import { apiFetch } from "../api/client"; import { closeReasonZh, @@ -59,6 +59,8 @@ type Fill = { qty_eth: number; }; +const PAGE_SIZE = 10; + function pnlClass(n: number | null | undefined) { if (n == null || Number.isNaN(n)) return ""; return n > 0 ? "pos-pnl-profit" : n < 0 ? "pos-pnl-loss" : ""; @@ -96,9 +98,14 @@ function fmtHold(ms: number | null | undefined) { return `${s}秒`; } +function groupPnl(g: Group) { + return g.net_pnl ?? g.pnl_summary?.net_pnl ?? g.realized_pnl; +} + export default function TradesPage() { const titleId = useId(); const [groups, setGroups] = useState([]); + const [page, setPage] = useState(1); const [selected, setSelected] = useState(null); const [selectedGroup, setSelectedGroup] = useState(null); const [fills, setFills] = useState([]); @@ -109,7 +116,10 @@ export default function TradesPage() { useEffect(() => { apiFetch<{ groups: Group[] }>("/api/trades/groups") - .then((r) => setGroups(r.groups)) + .then((r) => { + setGroups(r.groups); + setPage(1); + }) .catch((e) => setErr(e instanceof Error ? e.message : String(e))); }, []); @@ -127,6 +137,13 @@ export default function TradesPage() { }; }, [selected]); + const totalPages = Math.max(1, Math.ceil(groups.length / PAGE_SIZE)); + const safePage = Math.min(page, totalPages); + const pageGroups = useMemo(() => { + const start = (safePage - 1) * PAGE_SIZE; + return groups.slice(start, start + PAGE_SIZE); + }, [groups, safePage]); + function closeDetail() { setSelected(null); setSelectedGroup(null); @@ -159,6 +176,40 @@ export default function TradesPage() { } } + function renderMobileRow(g: Group) { + const listPnl = groupPnl(g); + const openMs = g.hold_open_at_ms ?? g.open_at_ms; + const closeMs = g.hold_close_at_ms ?? g.close_at_ms; + return ( + + ); + } + return (

    交易记录

    @@ -166,44 +217,94 @@ export default function TradesPage() { 持仓周期按目标平仓计时;净盈利达标且只平永续时,以永续平仓时间为准(不含期权残留至到期)。点击一条可查看明细。

    {err ?
    {err}
    : null} -
    + + {/* 手机端:保持原卡片列表 */} +
    {groups.length === 0 ? (

    暂无成交组

    ) : ( - groups.map((g) => { - const listPnl = - g.net_pnl ?? g.pnl_summary?.net_pnl ?? g.realized_pnl; - const openMs = g.hold_open_at_ms ?? g.open_at_ms; - const closeMs = g.hold_close_at_ms ?? g.close_at_ms; - return ( + groups.map((g) => renderMobileRow(g)) + )} +
    + + {/* 电脑端:表格 + 翻页 */} +
    + {groups.length === 0 ? ( +

    暂无成交组

    + ) : ( + <> +
    + + + + + + + + + + + + + + + + {pageGroups.map((g, i) => { + const listPnl = groupPnl(g); + const openMs = g.hold_open_at_ms ?? g.open_at_ms; + const closeMs = g.hold_close_at_ms ?? g.close_at_ms; + const seq = (safePage - 1) * PAGE_SIZE + i + 1; + return ( + openGroup(g.group_id)} + > + + + + + + + + + + + ); + })} + +
    序号组名方向持仓状态开仓时间平仓时间持仓时长盈亏金额平仓方式
    {seq}{g.group_id} + {positionSidesZh(g.perp_side, g.option_side)} + {statusZh(g.status)}{fmtTime(openMs)}{fmtTime(closeMs)}{fmtHold(g.hold_ms)} + {fmt(listPnl)} + + {g.close_reason + ? closeReasonZh(g.close_reason) + : "—"} +
    +
    +
    - ); - }) + + {safePage} / {totalPages} · 共 {groups.length} 组 + + +
    + )}
    diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 876300e..465995c 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -177,6 +177,12 @@ input { margin: 0 0 12px; } +@media (max-width: 900px) { + .funds-bar-shell { + display: none; + } +} + .funds-bar-inner { width: 100%; max-width: none; @@ -766,6 +772,81 @@ input { padding-bottom: 4px; } +/* 电脑端表格;手机端保持卡片列表 */ +.trade-list-desktop { + display: none; +} + +.trade-list-mobile { + display: block; +} + +.trade-table-card { + padding: 12px 14px 10px; + margin-bottom: 12px; +} + +.trade-table-wrap { + width: 100%; + overflow-x: auto; +} + +.trade-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.trade-table th, +.trade-table td { + padding: 10px 8px; + text-align: left; + border-bottom: 1px solid var(--line); + white-space: nowrap; + vertical-align: middle; +} + +.trade-table th { + color: var(--muted); + font-weight: 600; + font-size: 12px; +} + +.trade-table-row { + cursor: pointer; +} + +.trade-table-row:hover td { + background: rgba(240, 185, 11, 0.06); +} + +.trade-table-id { + font-weight: 600; +} + +.trade-pager { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + padding: 12px 0 4px; +} + +.trade-pager-info { + color: var(--muted); + font-size: 13px; +} + +@media (min-width: 901px) { + .trade-list-desktop { + display: block; + } + + .trade-list-mobile { + display: none; + } +} + .trade-row { display: flex; justify-content: space-between; @@ -1306,6 +1387,51 @@ input { gap: 10px; } +.backup-list { + display: flex; + flex-direction: column; + gap: 0; + border: 1px solid var(--line); + border-radius: 8px; + overflow: hidden; +} + +.backup-list-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border-bottom: 1px solid var(--line); + width: 100%; + box-sizing: border-box; +} + +.backup-list-row:last-child { + border-bottom: none; +} + +.backup-list-name { + flex: 1 1 auto; + min-width: 0; + word-break: break-all; + font-size: 13px; + line-height: 1.4; +} + +.backup-list-meta { + color: var(--muted); + font-weight: 400; +} + +/* 电脑端:一屏最多约 5 行,超出滚动 */ +@media (min-width: 901px) { + .backup-list { + max-height: calc(5 * 48px); + overflow-y: auto; + } +} + @media (min-width: 700px) { .settings-fields { grid-template-columns: repeat(2, minmax(0, 1fr));