|
|
|
@ -70,6 +70,16 @@ function initEventListeners() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新全部按钮
|
|
|
|
|
|
|
|
document.getElementById('refresh-all-btn').addEventListener('click', refreshAllSymbols);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 详情页刷新按钮
|
|
|
|
|
|
|
|
document.getElementById('refresh-symbol-btn').addEventListener('click', function() {
|
|
|
|
|
|
|
|
if (currentSymbol) {
|
|
|
|
|
|
|
|
refreshSingleSymbol(currentSymbol);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const savedTheme = localStorage.getItem('futures-theme');
|
|
|
|
const savedTheme = localStorage.getItem('futures-theme');
|
|
|
|
if (savedTheme === 'dark') {
|
|
|
|
if (savedTheme === 'dark') {
|
|
|
|
document.body.classList.remove('theme-minimal');
|
|
|
|
document.body.classList.remove('theme-minimal');
|
|
|
|
@ -321,6 +331,9 @@ function renderFuturesGrid(data) {
|
|
|
|
<span><span class="label">支撑</span> <span class="up">${formatNumber(item.support)}</span></span>
|
|
|
|
<span><span class="label">支撑</span> <span class="up">${formatNumber(item.support)}</span></span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
|
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
|
|
|
|
|
|
<button class="card-refresh-btn" onclick="event.stopPropagation(); refreshSingleSymbol('${item.symbol}', this)" title="刷新数据">
|
|
|
|
|
|
|
|
<i class="fas fa-sync-alt"></i>
|
|
|
|
|
|
|
|
</button>
|
|
|
|
<button class="watch-btn ${isWatched ? 'active' : ''}" onclick="toggleWatch('${item.symbol}', '${item.name}', event)" title="${isWatched ? '取消自选' : '加入自选'}">
|
|
|
|
<button class="watch-btn ${isWatched ? 'active' : ''}" onclick="toggleWatch('${item.symbol}', '${item.name}', event)" title="${isWatched ? '取消自选' : '加入自选'}">
|
|
|
|
<i class="fas fa-star"></i>
|
|
|
|
<i class="fas fa-star"></i>
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
@ -925,3 +938,169 @@ function calcEMA(data, period) {
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Toast 提示 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function showToast(type, title, message, duration = 3000) {
|
|
|
|
|
|
|
|
const container = document.getElementById('toast-container');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const iconMap = {
|
|
|
|
|
|
|
|
success: 'fas fa-check',
|
|
|
|
|
|
|
|
info: 'fas fa-info',
|
|
|
|
|
|
|
|
warning: 'fas fa-exclamation',
|
|
|
|
|
|
|
|
error: 'fas fa-times'
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const toast = document.createElement('div');
|
|
|
|
|
|
|
|
toast.className = `toast ${type}`;
|
|
|
|
|
|
|
|
toast.innerHTML = `
|
|
|
|
|
|
|
|
<div class="toast-icon"><i class="${iconMap[type]}"></i></div>
|
|
|
|
|
|
|
|
<div class="toast-content">
|
|
|
|
|
|
|
|
<div class="toast-title">${title}</div>
|
|
|
|
|
|
|
|
<div class="toast-message">${message}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
container.appendChild(toast);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
toast.classList.add('removing');
|
|
|
|
|
|
|
|
setTimeout(() => toast.remove(), 300);
|
|
|
|
|
|
|
|
}, duration);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 数据刷新功能 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let isRefreshing = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshAllSymbols() {
|
|
|
|
|
|
|
|
if (isRefreshing) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const btn = document.getElementById('refresh-all-btn');
|
|
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
btn.classList.add('spinning');
|
|
|
|
|
|
|
|
isRefreshing = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('info', '开始刷新', '正在同步所有品种数据...');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/refresh-all`, { method: 'POST' });
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
|
|
pollRefreshStatus();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
showToast('error', '刷新失败', data.message || '请稍后重试');
|
|
|
|
|
|
|
|
resetRefreshButton(btn);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('刷新全部失败:', error);
|
|
|
|
|
|
|
|
showToast('error', '刷新失败', '网络错误,请稍后重试');
|
|
|
|
|
|
|
|
resetRefreshButton(btn);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function pollRefreshStatus() {
|
|
|
|
|
|
|
|
const btn = document.getElementById('refresh-all-btn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/refresh-status`);
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success && data.data) {
|
|
|
|
|
|
|
|
const status = data.data;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!status.running) {
|
|
|
|
|
|
|
|
resetRefreshButton(btn);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await loadFuturesList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentSymbol) {
|
|
|
|
|
|
|
|
await loadFuturesDetail(currentSymbol);
|
|
|
|
|
|
|
|
await loadKlineData(currentSymbol, currentPeriod);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('success', '刷新完成', `已同步 ${status.total} 个品种数据`);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
btn.innerHTML = `<i class="fas fa-sync-alt"></i><span>刷新中 ${status.progress}/${status.total}</span>`;
|
|
|
|
|
|
|
|
setTimeout(pollRefreshStatus, 2000);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('获取刷新状态失败:', error);
|
|
|
|
|
|
|
|
resetRefreshButton(btn);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resetRefreshButton(btn) {
|
|
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
|
|
btn.classList.remove('spinning');
|
|
|
|
|
|
|
|
btn.innerHTML = '<i class="fas fa-sync-alt"></i><span>刷新全部</span>';
|
|
|
|
|
|
|
|
isRefreshing = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshSingleSymbol(symbol, btnElement = null) {
|
|
|
|
|
|
|
|
// 优先使用传入的按钮元素,其次尝试从事件获取,最后使用详情页按钮
|
|
|
|
|
|
|
|
let btn = btnElement;
|
|
|
|
|
|
|
|
if (!btn) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const evt = event;
|
|
|
|
|
|
|
|
if (evt && evt.target) {
|
|
|
|
|
|
|
|
const cardBtn = evt.target.closest('.card-refresh-btn');
|
|
|
|
|
|
|
|
if (cardBtn) {
|
|
|
|
|
|
|
|
btn = cardBtn;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
// event 不存在时忽略
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!btn) {
|
|
|
|
|
|
|
|
btn = document.getElementById('refresh-symbol-btn');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!btn) {
|
|
|
|
|
|
|
|
showToast('error', '刷新失败', '无法找到刷新按钮');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const originalContent = btn.innerHTML;
|
|
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('info', '检查数据', `正在检查 ${symbol} 数据新鲜度...`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE}/refresh/${symbol}`, { method: 'POST' });
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
|
|
if (data.refreshed) {
|
|
|
|
|
|
|
|
btn.innerHTML = '<i class="fas fa-check"></i>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await loadFuturesDetail(symbol);
|
|
|
|
|
|
|
|
await loadKlineData(symbol, currentPeriod);
|
|
|
|
|
|
|
|
await loadFuturesList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('success', '数据已更新', `${symbol} 最新数据已同步`);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
btn.innerHTML = '<i class="fas fa-check"></i>';
|
|
|
|
|
|
|
|
showToast('success', '数据新鲜', `${symbol} 数据仍在有效期内,无需刷新`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
|
|
btn.innerHTML = originalContent;
|
|
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
showToast('error', '刷新失败', data.message || '请稍后重试');
|
|
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
|
|
btn.innerHTML = originalContent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('刷新失败:', error);
|
|
|
|
|
|
|
|
showToast('error', '刷新失败', '网络错误,请稍后重试');
|
|
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
|
|
btn.innerHTML = originalContent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|