Add responsive mobile layout, records cards, and tablet settings fold fix.

Mobile gets compact trade/records UI with detail modals; static assets are cache-busted and settings cards fold correctly on tablet grid layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-29 16:42:38 +08:00
parent 44bec23296
commit c5262a0a54
14 changed files with 1465 additions and 35 deletions
+107 -5
View File
@@ -1,7 +1,11 @@
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
{% extends "base.html" %}
{% block title %}交易记录与复盘 - 国内期货 · 交易复盘系统{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/records.css') }}?v={{ asset_v }}">
{% endblock %}
{% block content %}
<div class="records-page">
<div class="card records-equity-card" style="margin-bottom:1.25rem">
<h2>资金曲线</h2>
<div class="card-body">
@@ -20,11 +24,61 @@
{% else %}
<p class="hint" style="margin-top:0">CTP 未连接时仅显示本地数据库记录;连接后打开本页会自动同步柜台成交。</p>
{% endif %}
<label class="trade-switch-label">
<label class="trade-switch-label records-desktop-only">
<input type="checkbox" id="trade-edit-switch">
<span>修改/核对开关(开启后可编辑关键字段)</span>
</label>
<div class="trade-table-wrap">
<div class="records-mobile-list" id="records-trade-mobile">
{% for t in trades %}
<button type="button" class="records-mobile-item records-trade-item" data-trade='{{ {
"symbol": t.symbol_name or t.symbol,
"symbol_code": t.symbol,
"monitor_type": t.monitor_type,
"source": "柜台" if t.source == "ctp" else "本地",
"direction": "做多" if t.direction == "long" else "做空",
"entry_price": t.entry_price,
"stop_loss": t.stop_loss,
"take_profit": t.take_profit,
"lots": t.lots,
"margin": t.margin,
"margin_pct": t.margin_pct,
"holding_minutes": t.holding_minutes or 0,
"open_time": t.open_time,
"close_time": t.close_time,
"pnl": t.pnl,
"fee": t.fee,
"pnl_net": t.pnl_net,
"equity_after": t.equity_after,
"result": t.result,
"verified": t.verified,
"fill_review_url": url_for("fill_review_from_trade", tid=t.id),
"del_url": url_for("del_trade", tid=t.id)
} | tojson }}'>
<div class="records-mobile-item-head">
<span class="records-mobile-symbol">{{ t.symbol_name or t.symbol }}</span>
<span class="badge dir">{{ '做多' if t.direction == 'long' else '做空' }}</span>
</div>
<div class="records-mobile-item-meta">
<span>{{ (t.close_time or t.open_time or '')[:16].replace('T', ' ') }}</span>
{% if t.result == '止盈' %}<span class="badge profit">{{ t.result }}</span>
{% elif t.result == '止损' %}<span class="badge loss">{{ t.result }}</span>
{% elif t.result in ('移动止盈', '保本止盈') %}<span class="badge profit">{{ t.result }}</span>
{% elif t.result == '手动平仓' %}<span class="badge result-manual">{{ t.result }}</span>
{% else %}<span class="badge result-external">{{ t.result }}</span>{% endif %}
<span>{{ '柜台' if t.source == 'ctp' else '本地' }}</span>
</div>
<div class="records-mobile-item-foot">
<span class="records-mobile-pnl {{ 'is-profit' if t.pnl_net and t.pnl_net > 0 else ('is-loss' if t.pnl_net and t.pnl_net < 0 else 'is-flat') }}">
净盈亏 {{ t.pnl_net if t.pnl_net is not none else '-' }}
</span>
<span class="records-mobile-chevron">查看详情 </span>
</div>
</button>
{% else %}
<p class="records-mobile-empty">暂无交易记录</p>
{% endfor %}
</div>
<div class="trade-table-wrap records-desktop-only">
<table class="trade-table">
<thead>
<tr>
@@ -243,7 +297,45 @@
<button type="submit" class="btn-primary">筛选</button>
</form>
{% endif %}
<div class="card-scroll">
<div class="records-mobile-list records-review-mobile">
{% for r in reviews %}
<button type="button" class="records-mobile-item review-view-btn{% if r.is_emotion %} is-emotion{% endif %}" data-review='{{ {
"symbol": r.symbol_name or r.symbol, "symbol_code": r.symbol,
"direction": "做多" if r.direction=="long" else "做空",
"lots": r.lots, "timeframe": r.timeframe,
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
"close_price": r.close_price,
"open_time": r.open_time, "close_time": r.close_time,
"holding_duration": r.holding_duration,
"initial_pnl": r.initial_pnl, "actual_pnl": r.actual_pnl, "pnl": r.pnl,
"fee": r.fee, "pnl_net": r.pnl_net,
"open_type": r.open_type,
"exit_trigger": r.exit_trigger, "exit_supplement": r.exit_supplement,
"watch_after_breakeven": r.watch_after_breakeven,
"new_position_while_occupied": r.new_position_while_occupied,
"is_emotion": r.is_emotion, "behavior_tags": r.behavior_tags,
"notes": r.notes, "screenshot": r.screenshot
} | tojson }}'>
<div class="records-mobile-item-head">
<span class="records-mobile-symbol">{{ r.symbol_name or r.symbol }}</span>
<span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span>
</div>
<div class="records-mobile-item-meta">
<span>{{ r.close_time[:16].replace('T', ' ') if r.close_time else '' }}</span>
{% if r.is_emotion %}<span class="badge emotion">情绪单</span>{% endif %}
</div>
<div class="records-mobile-item-foot">
<span class="records-mobile-pnl {{ 'is-profit' if r.pnl_net and r.pnl_net > 0 else ('is-loss' if r.pnl_net and r.pnl_net < 0 else 'is-flat') }}">
净盈亏 {{ r.pnl_net if r.pnl_net is not none else '-' }}
</span>
<span class="records-mobile-chevron">查看详情 </span>
</div>
</button>
{% else %}
<p class="records-mobile-empty">暂无复盘记录</p>
{% endfor %}
</div>
<div class="card-scroll records-desktop-only">
<table>
<thead>
<tr>
@@ -307,6 +399,14 @@
</div>
</div>
<div id="trade-detail-modal" class="modal-mask">
<div class="modal-box review-modal-fullscreen">
<span class="modal-close"></span>
<h3>交易详情</h3>
<div id="trade-detail-modal-body" class="review-modal-body"></div>
</div>
</div>
{% if auto_records %}
<div class="card" style="margin-top:1rem">
<h2>系统自动记录(止盈/止损)</h2>
@@ -327,13 +427,15 @@
</table>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script>window.__EQUITY_CURVE__ = {{ equity_curve | default([]) | tojson }};</script>
<script src="{{ url_for('static', filename='js/equity_curve.js') }}"></script>
<script src="{{ url_for('static', filename='js/review.js') }}"></script>
<script src="{{ url_for('static', filename='js/trades.js') }}"></script>
<script src="{{ url_for('static', filename='js/review.js') }}?v={{ asset_v }}"></script>
<script src="{{ url_for('static', filename='js/records.js') }}?v={{ asset_v }}"></script>
<script src="{{ url_for('static', filename='js/trades.js') }}?v={{ asset_v }}"></script>
{% if prefill %}
<script>
function bootReviewPrefill() {