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
+11 -1
View File
@@ -29,7 +29,9 @@
}
function isMobileNav() {
return window.matchMedia('(max-width: 767px)').matches;
if (window.qihuoLayout && window.qihuoLayout.isPhone()) return true;
return document.documentElement.dataset.mobile === '1'
|| window.matchMedia('(max-width: 767px)').matches;
}
toggle.addEventListener('click', function () {
@@ -70,6 +72,14 @@
if (!isMobileNav()) closeNav();
});
if (window.qihuoLayout) {
window.addEventListener('orientationchange', function () {
setTimeout(function () {
if (!isMobileNav()) closeNav();
}, 150);
});
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeNav();
});
+102
View File
@@ -0,0 +1,102 @@
/* Copyright (c) 2025-2026 马建军. All rights reserved.
* 专有软件 — 未经授权禁止复制、传播、转售。
* 详见 LICENSE.zh-CN.txt
*/
(function () {
function isCoarsePointer() {
return window.matchMedia('(hover: none) and (pointer: coarse)').matches;
}
function isTabletUa() {
var ua = navigator.userAgent || '';
if (/iPad/i.test(ua)) return true;
if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) return true;
if (/Android/i.test(ua) && !/Mobile/i.test(ua)) return true;
if (/Tablet|PlayBook|Silk/i.test(ua)) return true;
return false;
}
function isPhoneUa() {
var ua = navigator.userAgent || '';
if (isTabletUa()) return false;
if (/iPhone|iPod/i.test(ua)) return true;
if (/Android/i.test(ua) && /Mobile/i.test(ua)) return true;
if (/HarmonyOS|OpenHarmony/i.test(ua) && !/Tablet/i.test(ua)) return true;
if (/Mobile|Windows Phone|IEMobile|BlackBerry/i.test(ua)) return true;
return false;
}
function screenShortSide() {
return Math.min(window.screen.width || 0, window.screen.height || 0);
}
function detectLayout() {
var shortSide = screenShortSide();
var coarse = isCoarsePointer();
if (isPhoneUa()) return 'phone';
if (isTabletUa()) return 'tablet';
if (shortSide > 0 && shortSide < 600) return 'phone';
if (shortSide >= 600 && shortSide <= 1100 && coarse) return 'tablet';
if (window.innerWidth <= 767) return 'phone';
if (window.innerWidth <= 1100 && coarse) return 'tablet';
return 'desktop';
}
function updateLayoutState() {
var root = document.documentElement;
var layout = detectLayout();
var isPhone = layout === 'phone';
var landscape = window.innerWidth > window.innerHeight;
root.dataset.layout = layout;
root.dataset.mobile = isPhone ? '1' : '0';
root.dataset.orientation = isPhone ? 'portrait' : (landscape ? 'landscape' : 'portrait');
root.classList.toggle('layout-phone', isPhone);
root.classList.toggle('layout-tablet', layout === 'tablet');
var nav = document.getElementById('site-nav');
var userBar = document.querySelector('.user-bar');
if (nav && userBar && isPhone) {
nav.setAttribute('data-user-label', (userBar.textContent || '').replace(/\s+/g, ' ').trim());
}
var overlay = document.getElementById('orientation-lock');
var msg = document.getElementById('orientation-lock-msg');
if (!overlay) return;
if (isPhone) {
if (landscape) {
overlay.hidden = false;
if (msg) msg.textContent = '请竖屏使用';
} else {
overlay.hidden = true;
}
return;
}
if (layout === 'tablet' && root.dataset.orientation === 'portrait') {
overlay.hidden = false;
if (msg) msg.textContent = '平板请旋转至横屏使用';
return;
}
overlay.hidden = true;
}
window.qihuoLayout = {
isPhone: function () { return document.documentElement.dataset.mobile === '1'; },
isTablet: function () { return document.documentElement.dataset.layout === 'tablet'; },
refresh: updateLayoutState,
};
updateLayoutState();
window.addEventListener('resize', updateLayoutState);
window.addEventListener('orientationchange', function () {
setTimeout(updateLayoutState, 150);
});
document.addEventListener('visibilitychange', function () {
if (!document.hidden) updateLayoutState();
});
})();
+96
View File
@@ -0,0 +1,96 @@
/* Copyright (c) 2025-2026 马建军. All rights reserved.
* 交易记录 — 手机简洁列表与详情弹窗
*/
(function () {
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 pnlClass(v) {
var n = parseFloat(v);
if (isNaN(n) || n === 0) return 'is-flat';
return n > 0 ? 'is-profit' : 'is-loss';
}
function showTradeModal(data) {
var mask = document.getElementById('trade-detail-modal');
var body = document.getElementById('trade-detail-modal-body');
if (!mask || !body) return;
var fields = [
{ label: '品种', value: esc(data.symbol), wide: false },
{ label: '合约', value: esc(data.symbol_code), wide: false },
{ label: '类型', value: esc(data.monitor_type) + ' · ' + esc(data.source), wide: false },
{ label: '方向', value: esc(data.direction), wide: false },
{ label: '成交价', value: esc(data.entry_price), wide: false },
{ label: '手数', value: esc(data.lots), wide: false },
{ label: '止损', value: esc(data.stop_loss), wide: false },
{ label: '止盈', value: esc(data.take_profit), wide: false },
{ label: '保证金', value: data.margin != null ? esc(data.margin) : '-', wide: false },
{ label: '保证金占比', value: data.margin_pct != null ? esc(data.margin_pct) + '%' : '-', wide: false },
{ label: '持仓分钟', value: esc(data.holding_minutes), wide: false },
{ label: '开仓时间', value: fmtTime(data.open_time), wide: false },
{ label: '平仓时间', value: fmtTime(data.close_time), wide: false },
{ label: '盈亏(元)', value: esc(data.pnl), wide: false },
{ label: '手续费', value: esc(data.fee), wide: false },
{ label: '净盈亏', value: esc(data.pnl_net), wide: false },
{ label: '最新资金', value: esc(data.equity_after), wide: false },
{ label: '结果', value: esc(data.result) + (data.verified ? ' · 已核对' : ''), wide: true }
];
var html = '<div class="records-detail-grid">';
fields.forEach(function (f) {
html += '<div class="records-detail-item' + (f.wide ? ' wide' : '') + '">';
html += '<label>' + f.label + '</label><div>' + f.value + '</div></div>';
});
html += '</div>';
html += '<div class="records-detail-actions">';
if (data.fill_review_url) {
html += '<a href="' + data.fill_review_url + '" class="btn-fill">填入复盘</a>';
}
if (data.del_url) {
html += '<a href="' + data.del_url + '" class="btn-del" onclick="return confirm(\'删除?\')">删除</a>';
}
html += '</div>';
body.innerHTML = html;
mask.classList.add('show');
}
function bindTradeModal() {
var mask = document.getElementById('trade-detail-modal');
if (!mask) return;
var closeBtn = mask.querySelector('.modal-close');
if (closeBtn) {
closeBtn.addEventListener('click', function () {
mask.classList.remove('show');
});
}
mask.addEventListener('click', function (e) {
if (e.target === mask) mask.classList.remove('show');
});
document.querySelectorAll('.records-trade-item').forEach(function (btn) {
btn.addEventListener('click', function () {
try {
showTradeModal(JSON.parse(btn.getAttribute('data-trade')));
} catch (err) { /* ignore */ }
});
});
}
function bootRecordsPage() {
if (!document.querySelector('.records-page')) return;
bindTradeModal();
}
if (window.qihuoPageBoot) window.qihuoPageBoot(bootRecordsPage, '.records-page');
else if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootRecordsPage);
else document.addEventListener('DOMContentLoaded', bootRecordsPage);
})();