/* Copyright (c) 2025-2026 马建军. All rights reserved. * 交易日历 — 按日汇总平仓与复盘 */ (function () { var calYear = 0; var calMonth = 0; var selectedDate = ''; function pad2(n) { return n < 10 ? '0' + n : String(n); } function todayIso() { var d = new Date(); return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); } function fmtNum(v) { if (v === null || v === undefined || v === '') return '-'; var n = Number(v); if (isNaN(n)) return String(v); return Number.isInteger(n) ? String(n) : n.toFixed(2); } function fmtMoney(v) { if (v === null || v === undefined) return '-'; return fmtNum(v) + ' 元'; } function pnlClass(v) { if (v > 0) return 'is-profit'; if (v < 0) return 'is-loss'; return 'is-flat'; } function fmtPnlShort(v) { if (v === null || v === undefined) return '-'; var n = Number(v); if (isNaN(n)) return '-'; var s = Number.isInteger(n) ? String(n) : n.toFixed(0); return (n > 0 ? '+' : '') + s; } function fmtTime(v) { if (!v) return '-'; return String(v).replace('T', ' ').slice(0, 16); } function fmtTags(item) { var tags = item.behavior_tags || ''; if (item.is_emotion) { return tags ? '情绪单 · ' + tags : '情绪单'; } return tags || ''; } function setCalendarTitle() { var title = document.getElementById('trade-cal-title'); if (!title) return; if (window.qihuoLunar && qihuoLunar.monthTitle) { title.textContent = qihuoLunar.monthTitle(calYear, calMonth); } else { title.textContent = '公历 ' + calYear + '年' + calMonth + '月'; } } function lunarCellHtml(iso) { if (!window.qihuoLunar) return ''; var text = qihuoLunar.cellLunarText(iso); var info = qihuoLunar.fromIso(iso); var cls = 'trade-cal-day-lunar'; if (info.lunarDay === 1) cls += ' is-month-start'; return '' + text + ''; } function renderCalendar(data) { var grid = document.getElementById('trade-cal-grid'); if (!grid) return; setCalendarTitle(); var html = ''; var pad = data.weekday_start || 0; var i; for (i = 0; i < pad; i++) { html += '
'; } (data.days || []).forEach(function (day) { var dayNum = day.date.slice(8, 10).replace(/^0/, ''); var classes = ['trade-cal-cell']; if (day.count > 0) classes.push('is-clickable'); if (day.date === todayIso()) classes.push('is-today'); if (day.date === selectedDate) classes.push('is-selected'); if (day.has_emotion) classes.push('is-emotion'); html += '
'; html += '
'; html += '' + dayNum + ''; html += lunarCellHtml(day.date); html += '
'; if (day.count > 0) { html += '
' + day.count + ' 笔
'; html += '
' + fmtPnlShort(day.total_net) + '
'; if (day.has_emotion) { html += '情绪' + (day.emotion_count > 1 ? '×' + day.emotion_count : '') + ''; } html += '
'; } html += '
'; }); grid.innerHTML = html; } function loadCalendar() { fetch('/api/stats/calendar?year=' + calYear + '&month=' + calMonth, { credentials: 'same-origin' }) .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .then(function (data) { renderCalendar(data); if (selectedDate && selectedDate.slice(0, 7) === calYear + '-' + pad2(calMonth)) { loadDayDetail(selectedDate, false); } else { hideDayDetail(); } }) .catch(function () { var grid = document.getElementById('trade-cal-grid'); if (grid) grid.innerHTML = '
日历加载失败
'; }); } function hideDayDetail() { var panel = document.getElementById('trade-cal-day-detail'); if (panel) panel.hidden = true; } function renderDayDetail(data) { var panel = document.getElementById('trade-cal-day-detail'); var title = document.getElementById('trade-cal-day-detail-title'); var summary = document.getElementById('trade-cal-day-summary'); var list = document.getElementById('trade-cal-day-list'); if (!panel || !list) return; var label = data.date.replace(/-/g, '/'); var lunarPart = window.qihuoLunar ? '(' + qihuoLunar.daySubtitle(data.date) + ')' : ''; if (title) title.textContent = label + lunarPart + ' 交易记录'; if (summary) { var parts = [data.count + ' 笔', '净盈亏 ' + fmtMoney(data.total_net)]; if (data.emotion_count) parts.push('情绪单 ' + data.emotion_count); summary.textContent = parts.join(' · '); } list.innerHTML = ''; if (!data.items || !data.items.length) { list.innerHTML = '
当日无平仓记录
'; panel.hidden = false; return; } data.items.forEach(function (item) { var card = document.createElement('div'); card.className = 'trade-cal-day-item' + (item.is_emotion ? ' is-emotion' : ''); var head = document.createElement('div'); head.className = 'trade-cal-day-item-head'; var sym = document.createElement('div'); sym.className = 'trade-cal-day-item-symbol'; sym.textContent = (item.symbol || item.symbol_code || '-') + ' · ' + (item.direction || '-'); var pnl = document.createElement('div'); pnl.className = 'trade-cal-day-item-pnl ' + pnlClass(item.pnl_net); pnl.textContent = fmtMoney(item.pnl_net); head.appendChild(sym); head.appendChild(pnl); var meta = document.createElement('div'); meta.className = 'trade-cal-day-item-meta'; var badges = ''; if (item.source === 'review') { badges += '复盘'; } if (item.is_emotion) { badges += '情绪单'; } var metaParts = [ badges, '平仓 ' + fmtTime(item.close_time), item.lots != null ? item.lots + ' 手' : '', item.entry_price != null ? '开 ' + item.entry_price : '', item.close_price != null ? '平 ' + item.close_price : '', ]; if (item.source === 'review' && item.open_type) metaParts.push(item.open_type); if (item.source === 'review' && item.exit_trigger) metaParts.push('出场: ' + item.exit_trigger); if (item.result) metaParts.push(item.result); meta.innerHTML = metaParts.filter(Boolean).join(' · '); card.appendChild(head); card.appendChild(meta); var tags = fmtTags(item); if (tags) { var tagEl = document.createElement('div'); tagEl.className = 'trade-cal-day-item-notes'; tagEl.textContent = tags; card.appendChild(tagEl); } if (item.notes) { var notes = document.createElement('div'); notes.className = 'trade-cal-day-item-notes'; notes.textContent = item.notes; card.appendChild(notes); } if (item.screenshot) { var shot = document.createElement('div'); shot.className = 'trade-cal-day-item-shot'; shot.innerHTML = '复盘截图'; card.appendChild(shot); } list.appendChild(card); }); panel.hidden = false; } function loadDayDetail(dateStr, scroll) { if (scroll === undefined) scroll = true; selectedDate = dateStr; document.querySelectorAll('.trade-cal-cell.is-selected').forEach(function (el) { el.classList.remove('is-selected'); }); var cell = document.querySelector('.trade-cal-cell[data-date="' + dateStr + '"]'); if (cell) cell.classList.add('is-selected'); fetch('/api/stats/calendar/day?date=' + encodeURIComponent(dateStr), { credentials: 'same-origin' }) .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .then(function (data) { renderDayDetail(data); if (scroll) { var panel = document.getElementById('trade-cal-day-detail'); if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }) .catch(function () { var panel = document.getElementById('trade-cal-day-detail'); var list = document.getElementById('trade-cal-day-list'); if (panel && list) { list.innerHTML = '
加载失败
'; panel.hidden = false; } }); } function shiftCalendarMonth(delta) { calMonth += delta; if (calMonth > 12) { calMonth = 1; calYear += 1; } else if (calMonth < 1) { calMonth = 12; calYear -= 1; } loadCalendar(); } function bindCalendar() { var grid = document.getElementById('trade-cal-grid'); if (!grid || grid.dataset.tradeCalBound) return; grid.dataset.tradeCalBound = '1'; grid.addEventListener('click', function (e) { var cell = e.target.closest('.trade-cal-cell.is-clickable'); if (!cell) return; loadDayDetail(cell.getAttribute('data-date')); }); grid.addEventListener('keydown', function (e) { if (e.key !== 'Enter' && e.key !== ' ') return; var cell = e.target.closest('.trade-cal-cell.is-clickable'); if (!cell) return; e.preventDefault(); loadDayDetail(cell.getAttribute('data-date')); }); var prev = document.getElementById('trade-cal-prev'); var next = document.getElementById('trade-cal-next'); var todayBtn = document.getElementById('trade-cal-today'); if (prev) prev.addEventListener('click', function () { shiftCalendarMonth(-1); }); if (next) next.addEventListener('click', function () { shiftCalendarMonth(1); }); if (todayBtn) todayBtn.addEventListener('click', function () { var d = new Date(); calYear = d.getFullYear(); calMonth = d.getMonth() + 1; loadCalendar(); }); } function bootCalendarPage() { if (!document.getElementById('trade-cal-grid')) return; var d = new Date(); calYear = d.getFullYear(); calMonth = d.getMonth() + 1; bindCalendar(); loadCalendar(); var params = new URLSearchParams(window.location.search); var dateParam = params.get('date'); if (dateParam && /^\d{4}-\d{2}-\d{2}$/.test(dateParam)) { var parts = dateParam.split('-'); calYear = parseInt(parts[0], 10); calMonth = parseInt(parts[1], 10); loadCalendar(); setTimeout(function () { loadDayDetail(dateParam); }, 300); } } if (window.qihuoPageBoot) window.qihuoPageBoot(bootCalendarPage, '#trade-cal-grid'); else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootCalendarPage); else bootCalendarPage(); })();