修复关键位sina_code字段;复盘详情全屏两行;开单计划表单布局优化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-15 14:22:42 +08:00
parent eaf72a13fc
commit 58020b6e9c
5 changed files with 820 additions and 724 deletions
+1
View File
@@ -174,6 +174,7 @@ def init_db():
"ALTER TABLE order_plans ADD COLUMN sina_code TEXT", "ALTER TABLE order_plans ADD COLUMN sina_code TEXT",
"ALTER TABLE order_plans ADD COLUMN market_code TEXT", "ALTER TABLE order_plans ADD COLUMN market_code TEXT",
"ALTER TABLE key_monitors ADD COLUMN market_code TEXT", "ALTER TABLE key_monitors ADD COLUMN market_code TEXT",
"ALTER TABLE key_monitors ADD COLUMN sina_code TEXT",
"ALTER TABLE trade_records ADD COLUMN market_code TEXT", "ALTER TABLE trade_records ADD COLUMN market_code TEXT",
"ALTER TABLE order_plans ADD COLUMN plan_date TEXT", "ALTER TABLE order_plans ADD COLUMN plan_date TEXT",
"ALTER TABLE order_plans ADD COLUMN decision_reason TEXT", "ALTER TABLE order_plans ADD COLUMN decision_reason TEXT",
+68 -16
View File
@@ -59,30 +59,82 @@
}); });
} }
function esc(v) {
if (v === null || v === undefined || v === '') return '-';
return String(v);
}
function fmtTime(v) {
if (!v) return '-';
return String(v).replace('T', ' ').slice(0, 16);
}
function fmtRR(data) {
var init = data.initial_pnl;
var act = data.actual_pnl;
if (init && act) return init + ' / ' + act;
return act || init || '-';
}
function fmtTags(data) {
var tags = data.behavior_tags || '';
if (data.is_emotion && tags.indexOf('情绪') === -1) {
return tags ? '情绪单 · ' + tags : '情绪单';
}
return tags || '-';
}
function showModal(data) { function showModal(data) {
var mask = document.getElementById('review-modal'); var mask = document.getElementById('review-modal');
var body = document.getElementById('review-modal-body'); var body = document.getElementById('review-modal-body');
if (!mask || !body) return; if (!mask || !body) return;
var html = '<div class="modal-grid">';
var fields = [ var labels = [
['品种', data.symbol], ['方向', data.direction], '品种', '合约', '方向', '张数', '周期',
['成交价', data.entry_price], ['止', data.stop_loss], '成交价', '止损', '止', '平仓价',
['止盈', data.take_profit], ['平仓价', data.close_price], '开仓时间', '平仓时间', '持仓时长', '盈利金额', '盈亏比',
['张数', data.lots], ['开仓时间', data.open_time], '开仓类型', '行为标签'
['平仓时间', data.close_time], ['持仓时长', data.holding_duration],
['初始盈亏比', data.initial_pnl], ['实际盈亏比', data.actual_pnl],
['盈亏金额', data.pnl], ['开仓类型', data.open_type],
['离场触发', data.exit_trigger], ['离场补充', data.exit_supplement],
['情绪单', data.is_emotion ? '是' : '否'],
['行为标签', data.behavior_tags], ['备注', data.notes]
]; ];
fields.forEach(function (pair) { var values = [
html += '<div class="item"><label>' + pair[0] + '</label><div>' + (pair[1] || '-') + '</div></div>'; esc(data.symbol),
esc(data.symbol_code),
esc(data.direction),
esc(data.lots),
esc(data.timeframe),
esc(data.entry_price),
esc(data.stop_loss),
esc(data.take_profit),
esc(data.close_price),
fmtTime(data.open_time),
fmtTime(data.close_time),
esc(data.holding_duration),
esc(data.pnl),
fmtRR(data),
esc(data.open_type),
fmtTags(data)
];
var html = '<div class="review-detail-table">';
html += '<div class="review-detail-headers">';
labels.forEach(function (lb) {
html += '<span>' + lb + '</span>';
}); });
html += '</div>'; html += '</div><div class="review-detail-values">';
values.forEach(function (val, i) {
var cls = '';
if (i === 15 && data.is_emotion) cls = ' class="emotion-val"';
html += '<span' + cls + '>' + val + '</span>';
});
html += '</div></div>';
html += '<div class="review-detail-image">';
if (data.screenshot) { if (data.screenshot) {
html += '<div style="margin-top:1rem"><img src="/uploads/' + data.screenshot + '" style="max-width:100%;border-radius:8px"></div>'; html += '<img src="/uploads/' + data.screenshot + '" alt="复盘截图">';
} else {
html += '<div class="no-img">暂无截图 / K线图</div>';
} }
html += '</div>';
body.innerHTML = html; body.innerHTML = html;
mask.classList.add('show'); mask.classList.add('show');
} }
+42 -1
View File
@@ -103,7 +103,7 @@
} }
.page-wrap{max-width:1800px;margin:0 auto;min-height:100vh} .page-wrap{max-width:1800px;margin:0 auto;min-height:100vh}
.site-header{text-align:center;padding:1.5rem 1rem 1.25rem;border-bottom:1px solid var(--border-header);position:relative} .site-header{text-align:center;padding:1.5rem 1rem 1.25rem;border-bottom:1px solid var(--border-header);position:relative}
.site-title{font-size:1.75rem;font-weight:700;color:var(--text-title);margin-bottom:1rem;line-height:1.3} .site-title{font-size:1.75rem;font-weight:700;color:var(--text-title);margin-bottom:1.65rem;line-height:1.3}
.header-tools{position:absolute;top:1rem;left:1.5rem;display:flex;gap:.5rem;align-items:center} .header-tools{position:absolute;top:1rem;left:1.5rem;display:flex;gap:.5rem;align-items:center}
.theme-toggle{ .theme-toggle{
display:flex;align-items:center;gap:.35rem; display:flex;align-items:center;gap:.35rem;
@@ -290,6 +290,47 @@
.modal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.75rem;font-size:.9rem} .modal-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.75rem;font-size:.9rem}
.modal-grid .item label{color:var(--text-muted);font-size:.75rem;display:block} .modal-grid .item label{color:var(--text-muted);font-size:.75rem;display:block}
.modal-grid .item div{margin-top:.2rem;color:var(--text-primary)} .modal-grid .item div{margin-top:.2rem;color:var(--text-primary)}
.form-compact .line-plan-1{grid-template-columns:minmax(0,1fr) minmax(72px,0.45fr) minmax(0,1.6fr)}
.form-compact .line-plan-2{grid-template-columns:repeat(4,minmax(0,1fr)) auto;align-items:center}
.form-compact .field-short select{padding:.55rem .5rem}
.badge.emotion{background:var(--loss-bg);color:var(--loss);font-weight:600;border:1px solid var(--loss)}
tr.row-emotion td{color:var(--loss)}
tr.row-emotion td .badge.emotion{background:var(--loss);color:#fff;border-color:var(--loss)}
.modal-box.review-modal-wide{max-width:960px}
.modal-box.review-modal-fullscreen{
width:calc(100vw - 1.5rem);max-width:none;
height:calc(100vh - 1.5rem);max-height:none;
border-radius:12px;display:flex;flex-direction:column;
}
.modal-box.review-modal-fullscreen h3{flex-shrink:0}
#review-modal-body.review-modal-body{flex:1;overflow:auto;min-height:0}
.review-detail-table{margin-bottom:1rem;overflow-x:auto}
.review-detail-headers,.review-detail-values{
display:grid;
grid-template-columns:repeat(16,minmax(52px,1fr));
gap:.35rem .4rem;align-items:start;
}
.review-detail-headers span{
font-size:.68rem;color:var(--text-muted);
white-space:nowrap;line-height:1.3;
}
.review-detail-values span{
font-size:.82rem;color:var(--text-primary);
line-height:1.35;word-break:break-all;
}
.review-detail-values span.emotion-val{color:var(--loss);font-weight:600}
.review-detail-fields{padding:.25rem 0}
.review-detail-section{margin-bottom:.85rem}
.review-detail-section h4{font-size:.78rem;color:var(--text-label);margin-bottom:.45rem;font-weight:600}
.review-detail-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.45rem .65rem}
.review-detail-item label{display:block;font-size:.7rem;color:var(--text-muted);margin-bottom:.15rem}
.review-detail-item div{font-size:.85rem;color:var(--text-primary);line-height:1.35}
.review-detail-item.wide{grid-column:span 2}
.review-detail-item.full{grid-column:1/-1}
.review-detail-item.emotion div{color:var(--loss);font-weight:600}
.review-detail-image{flex-shrink:0;padding-top:.75rem;border-top:1px solid var(--table-border)}
.review-detail-image img{width:100%;border-radius:10px;border:1px solid var(--card-border)}
.review-detail-image .no-img{color:var(--text-muted);font-size:.85rem;padding:2rem;text-align:center;background:var(--card-inner);border-radius:10px}
.modal-close{float:right;color:var(--text-muted);cursor:pointer;font-size:1.2rem} .modal-close{float:right;color:var(--text-muted);cursor:pointer;font-size:1.2rem}
.calc-readonly{background:var(--calc-bg);color:var(--accent)} .calc-readonly{background:var(--calc-bg);color:var(--accent)}
@media(max-width:1100px){ @media(max-width:1100px){
+5 -7
View File
@@ -6,11 +6,11 @@
<span class="card-corner tl"></span><span class="card-corner br"></span> <span class="card-corner tl"></span><span class="card-corner br"></span>
<h2>今日计划 <span class="text-muted" style="font-size:.8rem;font-weight:normal">今日 {{ today }}</span></h2> <h2>今日计划 <span class="text-muted" style="font-size:.8rem;font-weight:normal">今日 {{ today }}</span></h2>
<div class="card-body"> <div class="card-body">
<p class="hint" style="margin-bottom:.75rem">开盘前制定,当日有效;下方为进行中计划。</p> <p class="hint" style="margin-bottom:.75rem">开盘前制定,当日有效;请先选择<strong>主力合约</strong>下方为进行中计划。</p>
<form action="{{ url_for('add_plan') }}" method="post" class="form-compact"> <form action="{{ url_for('add_plan') }}" method="post" class="form-compact">
<div class="form-line line-3"> <div class="form-line line-plan-1">
<div class="symbol-wrap"> <div class="symbol-wrap">
<input type="text" class="symbol-input" placeholder="品种" autocomplete="off" required> <input type="text" class="symbol-input" placeholder="主力合约" autocomplete="off" required>
<input type="hidden" name="symbol" required> <input type="hidden" name="symbol" required>
<input type="hidden" name="symbol_name"> <input type="hidden" name="symbol_name">
<input type="hidden" name="market_code" required> <input type="hidden" name="market_code" required>
@@ -18,20 +18,18 @@
<div class="symbol-dropdown"></div> <div class="symbol-dropdown"></div>
<div class="symbol-selected"></div> <div class="symbol-selected"></div>
</div> </div>
<select name="direction" required> <select name="direction" class="field-short" required>
<option value="">方向</option> <option value="">方向</option>
<option value="long">做多</option> <option value="long">做多</option>
<option value="short">做空</option> <option value="short">做空</option>
</select> </select>
<input name="decision_reason" type="text" placeholder="决策理由"> <input name="decision_reason" type="text" placeholder="决策理由">
</div> </div>
<div class="form-line line-4"> <div class="form-line line-plan-2">
<input name="zone_lower" type="number" step="0.0001" placeholder="决策区间下限" required> <input name="zone_lower" type="number" step="0.0001" placeholder="决策区间下限" required>
<input name="zone_upper" type="number" step="0.0001" placeholder="决策区间上限" required> <input name="zone_upper" type="number" step="0.0001" placeholder="决策区间上限" required>
<input name="stop_loss" type="number" step="0.0001" placeholder="止损" required> <input name="stop_loss" type="number" step="0.0001" placeholder="止损" required>
<input name="take_profit" type="number" step="0.0001" placeholder="止盈" required> <input name="take_profit" type="number" step="0.0001" placeholder="止盈" required>
</div>
<div class="form-line line-btn">
<button type="submit" class="btn-primary">添加</button> <button type="submit" class="btn-primary">添加</button>
</div> </div>
</form> </form>
+12 -8
View File
@@ -107,7 +107,7 @@
</thead> </thead>
<tbody> <tbody>
{% for r in reviews %} {% for r in reviews %}
<tr> <tr class="{% if r.is_emotion %}row-emotion{% endif %}">
<td>{{ r.close_time[:16] if r.close_time else '' }}</td> <td>{{ r.close_time[:16] if r.close_time else '' }}</td>
<td>{{ r.symbol_name or r.symbol }}</td> <td>{{ r.symbol_name or r.symbol }}</td>
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td> <td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
@@ -116,17 +116,21 @@
{% elif r.pnl and r.pnl < 0 %}<span class="badge loss">{{ r.pnl }}</span> {% elif r.pnl and r.pnl < 0 %}<span class="badge loss">{{ r.pnl }}</span>
{% else %}{{ r.actual_pnl or '-' }}{% endif %} {% else %}{{ r.actual_pnl or '-' }}{% endif %}
</td> </td>
<td>{% if r.is_emotion %}<span class="badge loss">情绪</span>{% else %}-{% endif %}</td> <td>{% if r.is_emotion %}<span class="badge emotion">情绪</span>{% else %}-{% endif %}</td>
<td> <td>
<button type="button" class="btn-link review-view-btn" data-review='{{ { <button type="button" class="btn-link review-view-btn" data-review='{{ {
"symbol": r.symbol_name or r.symbol, "symbol_code": r.symbol, "direction": "做多" if r.direction=="long" else "做空", "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, "entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
"close_price": r.close_price, "lots": r.lots, "close_price": r.close_price,
"open_time": r.open_time, "close_time": r.close_time, "open_time": r.open_time, "close_time": r.close_time,
"holding_duration": r.holding_duration, "initial_pnl": r.initial_pnl, "holding_duration": r.holding_duration,
"actual_pnl": r.actual_pnl, "pnl": r.pnl, "initial_pnl": r.initial_pnl, "actual_pnl": r.actual_pnl, "pnl": r.pnl,
"open_type": r.open_type, "open_type": r.open_type,
"exit_trigger": r.exit_trigger, "exit_supplement": r.exit_supplement, "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, "is_emotion": r.is_emotion, "behavior_tags": r.behavior_tags,
"notes": r.notes, "screenshot": r.screenshot "notes": r.notes, "screenshot": r.screenshot
} | tojson }}'>放大查看</button> } | tojson }}'>放大查看</button>
@@ -144,10 +148,10 @@
</div> </div>
<div id="review-modal" class="modal-mask"> <div id="review-modal" class="modal-mask">
<div class="modal-box"> <div class="modal-box review-modal-fullscreen">
<span class="modal-close"></span> <span class="modal-close"></span>
<h3>复盘详情</h3> <h3>复盘详情</h3>
<div id="review-modal-body"></div> <div id="review-modal-body" class="review-modal-body"></div>
</div> </div>
</div> </div>