trading.ai/web/static/js/main.js
2025-09-18 23:21:16 +08:00

423 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* A股量化交易系统 主要JavaScript功能
*/
$(document).ready(function() {
// 初始化组件
initializeComponents();
// 绑定事件
bindEvents();
// 自动刷新
setupAutoRefresh();
});
/**
* 初始化组件
*/
function initializeComponents() {
// 初始化工具提示
$('[data-bs-toggle="tooltip"]').tooltip();
// 初始化弹出框
$('[data-bs-toggle="popover"]').popover();
// 表格排序
initializeTableSorting();
// 数字格式化
formatNumbers();
}
/**
* 绑定事件
*/
function bindEvents() {
// 表格行点击事件
$('.table tbody tr').on('click', function() {
$(this).toggleClass('table-active');
});
// 筛选表单自动提交
$('.auto-submit').on('change', function() {
$(this).closest('form').submit();
});
// 复制股票代码
$('.stock-code').on('click', function() {
const stockCode = $(this).text().trim();
copyToClipboard(stockCode);
showToast('股票代码已复制: ' + stockCode);
});
// 全选/取消全选
$('#selectAll').on('change', function() {
$('.row-select').prop('checked', this.checked);
});
// 导出数据
$('.export-btn').on('click', function() {
const format = $(this).data('format');
exportData(format);
});
}
/**
* 设置自动刷新
*/
function setupAutoRefresh() {
// 每5分钟自动刷新数据
setInterval(function() {
if (document.visibilityState === 'visible') {
refreshData();
}
}, 300000); // 5分钟
// 页面可见时刷新
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
const lastRefresh = localStorage.getItem('lastRefresh');
const now = Date.now();
// 如果超过5分钟未刷新则自动刷新
if (!lastRefresh || (now - parseInt(lastRefresh)) > 300000) {
refreshData();
}
}
});
}
/**
* 刷新数据
*/
function refreshData() {
// 显示加载状态
showLoading();
// 记录刷新时间
localStorage.setItem('lastRefresh', Date.now().toString());
// 刷新页面数据
if (typeof updatePageData === 'function') {
updatePageData();
} else {
// 默认重新加载页面
setTimeout(() => {
location.reload();
}, 1000);
}
}
/**
* 显示加载状态
*/
function showLoading() {
const loadingHtml = '<div class="loading-overlay"><div class="loading"></div></div>';
$('body').append(loadingHtml);
setTimeout(() => {
$('.loading-overlay').remove();
}, 2000);
}
/**
* 显示提示消息
*/
function showToast(message, type = 'success') {
const toastHtml = `
<div class="toast align-items-center text-white bg-${type} border-0" role="alert">
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
// 创建toast容器如果不存在
if (!$('.toast-container').length) {
$('body').append('<div class="toast-container position-fixed top-0 end-0 p-3"></div>');
}
const $toast = $(toastHtml);
$('.toast-container').append($toast);
// 显示toast
const toast = new bootstrap.Toast($toast[0]);
toast.show();
// 自动移除
$toast.on('hidden.bs.toast', function() {
$(this).remove();
});
}
/**
* 复制到剪贴板
*/
function copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
} else {
// 备用方法
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
}
/**
* 初始化表格排序
*/
function initializeTableSorting() {
$('.sortable th').on('click', function() {
const table = $(this).closest('table');
const column = $(this).index();
const order = $(this).hasClass('asc') ? 'desc' : 'asc';
// 移除其他列的排序样式
$(this).siblings().removeClass('asc desc');
// 添加当前列的排序样式
$(this).removeClass('asc desc').addClass(order);
// 排序表格行
sortTable(table, column, order);
});
}
/**
* 表格排序
*/
function sortTable(table, column, order) {
const tbody = table.find('tbody');
const rows = tbody.find('tr').toArray();
rows.sort(function(a, b) {
const aVal = $(a).find('td').eq(column).text().trim();
const bVal = $(b).find('td').eq(column).text().trim();
// 尝试数字比较
if (!isNaN(aVal) && !isNaN(bVal)) {
return order === 'asc' ? aVal - bVal : bVal - aVal;
}
// 字符串比较
return order === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
});
tbody.empty().append(rows);
}
/**
* 数字格式化
*/
function formatNumbers() {
$('.format-number').each(function() {
const value = parseFloat($(this).text());
if (!isNaN(value)) {
$(this).text(value.toLocaleString());
}
});
$('.format-percentage').each(function() {
const value = parseFloat($(this).text());
if (!isNaN(value)) {
$(this).text(value.toFixed(2) + '%');
}
});
$('.format-currency').each(function() {
const value = parseFloat($(this).text());
if (!isNaN(value)) {
$(this).text('¥' + value.toFixed(2));
}
});
}
/**
* 时间格式化 - 转换为东八区时间
*/
function formatDatetimeToChina(datetimeStr, format = '%m-%d %H:%M') {
if (!datetimeStr) return '';
let date;
try {
// 解析时间字符串
if (datetimeStr.endsWith('Z')) {
// UTC时间
date = new Date(datetimeStr);
} else if (datetimeStr.includes('T') && !datetimeStr.includes('+')) {
// 假设是UTC时间
date = new Date(datetimeStr + 'Z');
} else {
date = new Date(datetimeStr);
}
// 转换为东八区时间
const chinaTime = new Date(date.getTime() + (8 * 60 * 60 * 1000) - (date.getTimezoneOffset() * 60 * 1000));
// 格式化显示
const year = chinaTime.getFullYear();
const month = String(chinaTime.getMonth() + 1).padStart(2, '0');
const day = String(chinaTime.getDate()).padStart(2, '0');
const hour = String(chinaTime.getHours()).padStart(2, '0');
const minute = String(chinaTime.getMinutes()).padStart(2, '0');
// 根据格式返回
if (format === '%Y-%m-%d') {
return `${year}-${month}-${day}`;
} else if (format === '%m-%d %H:%M') {
return `${month}-${day} ${hour}:${minute}`;
} else {
return `${year}-${month}-${day} ${hour}:${minute}`;
}
} catch (e) {
console.error('时间格式化错误:', e);
return datetimeStr;
}
}
/**
* 导出数据
*/
function exportData(format) {
const table = $('.table').first();
if (!table.length) {
showToast('没有可导出的数据', 'warning');
return;
}
switch (format) {
case 'csv':
exportToCSV(table);
break;
case 'excel':
exportToExcel(table);
break;
case 'json':
exportToJSON(table);
break;
default:
showToast('不支持的导出格式', 'error');
}
}
/**
* 导出为CSV
*/
function exportToCSV(table) {
let csv = '';
// 表头
table.find('thead tr').each(function() {
const row = [];
$(this).find('th').each(function() {
row.push('"' + $(this).text().trim() + '"');
});
csv += row.join(',') + '\n';
});
// 数据行
table.find('tbody tr').each(function() {
const row = [];
$(this).find('td').each(function() {
row.push('"' + $(this).text().trim() + '"');
});
csv += row.join(',') + '\n';
});
// 下载文件
downloadFile(csv, 'trading_signals.csv', 'text/csv');
}
/**
* 下载文件
*/
function downloadFile(content, filename, contentType) {
const blob = new Blob([content], { type: contentType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
/**
* 获取API数据
*/
function apiRequest(endpoint, params = {}) {
return $.ajax({
url: '/api/' + endpoint,
method: 'GET',
data: params,
dataType: 'json'
});
}
/**
* 实时更新信号数据
*/
function updateSignals() {
apiRequest('signals', { limit: 10 })
.done(function(response) {
if (response.success && response.data.length > 0) {
updateSignalTable(response.data);
showToast('信号数据已更新');
}
})
.fail(function() {
showToast('更新信号数据失败', 'error');
});
}
/**
* 更新信号表格
*/
function updateSignalTable(signals) {
const tbody = $('.signals-table tbody');
tbody.empty();
signals.forEach(function(signal) {
const scanTime = signal.scan_time ? formatDatetimeToChina(signal.scan_time, '%m-%d %H:%M') : '';
const signalDate = signal.signal_date ? formatDatetimeToChina(signal.signal_date, '%Y-%m-%d') : '';
const row = `
<tr>
<td><span class="badge bg-secondary">${signal.stock_code}</span></td>
<td class="fw-bold">${signal.stock_name || '未知'}</td>
<td><span class="badge bg-info">${signal.strategy_name}</span></td>
<td>${signal.timeframe}</td>
<td>${signalDate}</td>
<td class="text-success fw-bold">${signal.breakout_price.toFixed(2)}元</td>
<td>${signal.yin_high.toFixed(2)}元</td>
<td><span class="badge bg-success">${signal.breakout_pct.toFixed(2)}%</span></td>
<td>${signal.final_yang_entity_ratio.toFixed(2)}%</td>
<td>${signal.turnover_ratio.toFixed(2)}%</td>
<td class="text-muted small">${scanTime}</td>
</tr>
`;
tbody.append(row);
});
}
// 全局错误处理
window.addEventListener('error', function(e) {
console.error('JavaScript Error:', e.error);
showToast('页面发生错误,请刷新重试', 'error');
});
// 网络状态监控
window.addEventListener('online', function() {
showToast('网络连接已恢复');
});
window.addEventListener('offline', function() {
showToast('网络连接已断开', 'warning');
});