修复关键位sina_code字段;复盘详情全屏两行;开单计划表单布局优化
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+165
-113
@@ -1,113 +1,165 @@
|
||||
(function () {
|
||||
function parseNum(v) {
|
||||
var n = parseFloat(v);
|
||||
return isNaN(n) ? null : n;
|
||||
}
|
||||
|
||||
function calcRR(direction, entry, stop, target) {
|
||||
if (!entry || !stop || !target) return '';
|
||||
var risk, reward;
|
||||
if (direction === 'long') {
|
||||
risk = entry - stop;
|
||||
reward = target - entry;
|
||||
} else if (direction === 'short') {
|
||||
risk = stop - entry;
|
||||
reward = entry - target;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
if (risk <= 0) return '';
|
||||
return (reward / risk).toFixed(2);
|
||||
}
|
||||
|
||||
function calcDuration(openVal, closeVal) {
|
||||
if (!openVal || !closeVal) return '';
|
||||
var o = new Date(openVal);
|
||||
var c = new Date(closeVal);
|
||||
var secs = Math.floor((c - o) / 1000);
|
||||
if (secs < 0) return '';
|
||||
var h = Math.floor(secs / 3600);
|
||||
var m = Math.floor((secs % 3600) / 60);
|
||||
return h ? h + '小时' + m + '分钟' : m + '分钟';
|
||||
}
|
||||
|
||||
function recalc() {
|
||||
var form = document.getElementById('review-form');
|
||||
if (!form) return;
|
||||
var dir = form.querySelector('[name="direction"]').value;
|
||||
var entry = parseNum(form.querySelector('[name="entry_price"]').value);
|
||||
var sl = parseNum(form.querySelector('[name="stop_loss"]').value);
|
||||
var tp = parseNum(form.querySelector('[name="take_profit"]').value);
|
||||
var close = parseNum(form.querySelector('[name="close_price"]').value);
|
||||
var openT = form.querySelector('[name="open_time"]').value;
|
||||
var closeT = form.querySelector('[name="close_time"]').value;
|
||||
|
||||
var hold = document.getElementById('holding_duration');
|
||||
var initR = document.getElementById('initial_rr');
|
||||
var actR = document.getElementById('actual_rr');
|
||||
if (hold) hold.value = calcDuration(openT, closeT);
|
||||
if (initR) initR.value = calcRR(dir, entry, sl, tp);
|
||||
if (actR) actR.value = calcRR(dir, entry, sl, close);
|
||||
}
|
||||
|
||||
function bindForm() {
|
||||
var form = document.getElementById('review-form');
|
||||
if (!form) return;
|
||||
form.querySelectorAll('input, select').forEach(function (el) {
|
||||
el.addEventListener('input', recalc);
|
||||
el.addEventListener('change', recalc);
|
||||
});
|
||||
}
|
||||
|
||||
function showModal(data) {
|
||||
var mask = document.getElementById('review-modal');
|
||||
var body = document.getElementById('review-modal-body');
|
||||
if (!mask || !body) return;
|
||||
var html = '<div class="modal-grid">';
|
||||
var fields = [
|
||||
['品种', 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) {
|
||||
html += '<div class="item"><label>' + pair[0] + '</label><div>' + (pair[1] || '-') + '</div></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
if (data.screenshot) {
|
||||
html += '<div style="margin-top:1rem"><img src="/uploads/' + data.screenshot + '" style="max-width:100%;border-radius:8px"></div>';
|
||||
}
|
||||
body.innerHTML = html;
|
||||
mask.classList.add('show');
|
||||
}
|
||||
|
||||
function bindModal() {
|
||||
var mask = document.getElementById('review-modal');
|
||||
if (!mask) return;
|
||||
mask.querySelector('.modal-close').addEventListener('click', function () {
|
||||
mask.classList.remove('show');
|
||||
});
|
||||
mask.addEventListener('click', function (e) {
|
||||
if (e.target === mask) mask.classList.remove('show');
|
||||
});
|
||||
document.querySelectorAll('.review-view-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
try {
|
||||
showModal(JSON.parse(btn.getAttribute('data-review')));
|
||||
} catch (e) { /* ignore */ }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
bindForm();
|
||||
bindModal();
|
||||
recalc();
|
||||
});
|
||||
})();
|
||||
(function () {
|
||||
function parseNum(v) {
|
||||
var n = parseFloat(v);
|
||||
return isNaN(n) ? null : n;
|
||||
}
|
||||
|
||||
function calcRR(direction, entry, stop, target) {
|
||||
if (!entry || !stop || !target) return '';
|
||||
var risk, reward;
|
||||
if (direction === 'long') {
|
||||
risk = entry - stop;
|
||||
reward = target - entry;
|
||||
} else if (direction === 'short') {
|
||||
risk = stop - entry;
|
||||
reward = entry - target;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
if (risk <= 0) return '';
|
||||
return (reward / risk).toFixed(2);
|
||||
}
|
||||
|
||||
function calcDuration(openVal, closeVal) {
|
||||
if (!openVal || !closeVal) return '';
|
||||
var o = new Date(openVal);
|
||||
var c = new Date(closeVal);
|
||||
var secs = Math.floor((c - o) / 1000);
|
||||
if (secs < 0) return '';
|
||||
var h = Math.floor(secs / 3600);
|
||||
var m = Math.floor((secs % 3600) / 60);
|
||||
return h ? h + '小时' + m + '分钟' : m + '分钟';
|
||||
}
|
||||
|
||||
function recalc() {
|
||||
var form = document.getElementById('review-form');
|
||||
if (!form) return;
|
||||
var dir = form.querySelector('[name="direction"]').value;
|
||||
var entry = parseNum(form.querySelector('[name="entry_price"]').value);
|
||||
var sl = parseNum(form.querySelector('[name="stop_loss"]').value);
|
||||
var tp = parseNum(form.querySelector('[name="take_profit"]').value);
|
||||
var close = parseNum(form.querySelector('[name="close_price"]').value);
|
||||
var openT = form.querySelector('[name="open_time"]').value;
|
||||
var closeT = form.querySelector('[name="close_time"]').value;
|
||||
|
||||
var hold = document.getElementById('holding_duration');
|
||||
var initR = document.getElementById('initial_rr');
|
||||
var actR = document.getElementById('actual_rr');
|
||||
if (hold) hold.value = calcDuration(openT, closeT);
|
||||
if (initR) initR.value = calcRR(dir, entry, sl, tp);
|
||||
if (actR) actR.value = calcRR(dir, entry, sl, close);
|
||||
}
|
||||
|
||||
function bindForm() {
|
||||
var form = document.getElementById('review-form');
|
||||
if (!form) return;
|
||||
form.querySelectorAll('input, select').forEach(function (el) {
|
||||
el.addEventListener('input', recalc);
|
||||
el.addEventListener('change', recalc);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
var mask = document.getElementById('review-modal');
|
||||
var body = document.getElementById('review-modal-body');
|
||||
if (!mask || !body) return;
|
||||
|
||||
var labels = [
|
||||
'品种', '合约', '方向', '张数', '周期',
|
||||
'成交价', '止损', '止盈', '平仓价',
|
||||
'开仓时间', '平仓时间', '持仓时长', '盈利金额', '盈亏比',
|
||||
'开仓类型', '行为标签'
|
||||
];
|
||||
var values = [
|
||||
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><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) {
|
||||
html += '<img src="/uploads/' + data.screenshot + '" alt="复盘截图">';
|
||||
} else {
|
||||
html += '<div class="no-img">暂无截图 / K线图</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
body.innerHTML = html;
|
||||
mask.classList.add('show');
|
||||
}
|
||||
|
||||
function bindModal() {
|
||||
var mask = document.getElementById('review-modal');
|
||||
if (!mask) return;
|
||||
mask.querySelector('.modal-close').addEventListener('click', function () {
|
||||
mask.classList.remove('show');
|
||||
});
|
||||
mask.addEventListener('click', function (e) {
|
||||
if (e.target === mask) mask.classList.remove('show');
|
||||
});
|
||||
document.querySelectorAll('.review-view-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
try {
|
||||
showModal(JSON.parse(btn.getAttribute('data-review')));
|
||||
} catch (e) { /* ignore */ }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
bindForm();
|
||||
bindModal();
|
||||
recalc();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user