// 股票数据展示系统前端应用
// 主应用入口文件 - 更新于 $(date)
class StockDataApp {
constructor() {
this.currentPage = 1;
this.pageSize = 20;
this.currentStock = null;
this.klineChart = null;
this.init();
}
// 初始化应用
async init() {
this.setupEventListeners();
await this.loadSystemOverview();
await this.loadStockData();
this.setupNavigation();
}
// 设置事件监听器
setupEventListeners() {
// 搜索功能
const searchInput = document.getElementById('stock-search');
const searchBtn = document.getElementById('search-btn');
searchBtn.addEventListener('click', () => this.searchStocks());
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.searchStocks();
});
// 股票选择器
const stockSelector = document.getElementById('stock-selector');
stockSelector.addEventListener('change', (e) => {
this.currentStock = e.target.value;
if (this.currentStock) this.loadKlineChart();
});
// 周期选择器
const periodSelector = document.getElementById('period-selector');
periodSelector.addEventListener('change', () => {
if (this.currentStock) this.loadKlineChart();
});
// 财务数据选择器
const financialStockSelector = document.getElementById('financial-stock-selector');
financialStockSelector.addEventListener('change', (e) => {
if (e.target.value) this.loadFinancialData(e.target.value);
});
// 日志刷新
const refreshLogsBtn = document.getElementById('refresh-logs');
refreshLogsBtn.addEventListener('click', () => this.loadSystemLogs());
// 日志过滤器
const logLevelFilter = document.getElementById('log-level-filter');
logLevelFilter.addEventListener('change', () => this.loadSystemLogs());
const logDateFilter = document.getElementById('log-date-filter');
logDateFilter.addEventListener('change', () => this.loadSystemLogs());
}
// 设置导航
setupNavigation() {
const navLinks = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('.content-section');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
// 移除所有激活状态
navLinks.forEach(l => l.classList.remove('active'));
sections.forEach(s => s.classList.remove('active'));
// 添加当前激活状态
link.classList.add('active');
const targetSection = document.querySelector(link.getAttribute('href'));
if (targetSection) {
targetSection.classList.add('active');
// 加载对应数据
switch(link.getAttribute('href')) {
case '#kline-chart':
this.loadKlineChart();
break;
case '#financial-data':
this.loadFinancialData();
break;
case '#system-logs':
this.loadSystemLogs();
break;
}
}
});
});
}
// 显示加载遮罩
showLoading() {
document.getElementById('loading-overlay').classList.add('show');
}
// 隐藏加载遮罩
hideLoading() {
document.getElementById('loading-overlay').classList.remove('show');
}
// 加载系统概览数据
async loadSystemOverview() {
try {
const response = await this.apiCall('/api/system/overview');
if (response.success) {
document.getElementById('stock-count').textContent = this.formatNumber(response.stock_count);
document.getElementById('kline-count').textContent = this.formatNumber(response.kline_count);
document.getElementById('financial-count').textContent = this.formatNumber(response.financial_count);
document.getElementById('log-count').textContent = this.formatNumber(response.log_count);
}
} catch (error) {
console.error('加载系统概览失败:', error);
}
}
// 加载股票数据
async loadStockData(page = 1) {
try {
this.showLoading();
const response = await this.apiCall(`/api/stocks?page=${page}&limit=${this.pageSize}`);
if (response.success) {
this.renderStockTable(response.data);
this.setupPagination(response.total, page);
this.populateStockSelectors(response.data);
}
} catch (error) {
console.error('加载股票数据失败:', error);
this.showError('加载股票数据失败');
} finally {
this.hideLoading();
}
}
// 渲染股票表格
renderStockTable(stocks) {
const tbody = document.getElementById('stock-table-body');
tbody.innerHTML = '';
stocks.forEach(stock => {
const row = document.createElement('tr');
row.innerHTML = `
${stock.code} |
${stock.name} |
${stock.exchange} |
${this.formatDate(stock.listing_date)} |
${stock.industry || '-'} |
|
`;
tbody.appendChild(row);
});
}
// 设置分页
setupPagination(total, currentPage) {
const pagination = document.getElementById('stock-pagination');
const totalPages = Math.ceil(total / this.pageSize);
if (totalPages <= 1) {
pagination.innerHTML = '';
return;
}
let html = '';
// 上一页按钮
if (currentPage > 1) {
html += ``;
}
// 页码按钮 - 智能显示头尾和当前页附近的页码
const maxVisiblePages = 7; // 最多显示7个页码按钮
const halfVisible = Math.floor(maxVisiblePages / 2);
// 总是显示第一页
if (currentPage === 1) {
html += ``;
} else {
html += ``;
}
// 计算起始和结束页码
let startPage = Math.max(2, currentPage - halfVisible);
let endPage = Math.min(totalPages - 1, currentPage + halfVisible);
// 如果当前页靠近开头,调整结束页码
if (currentPage <= halfVisible + 1) {
endPage = Math.min(totalPages - 1, maxVisiblePages - 1);
}
// 如果当前页靠近结尾,调整起始页码
if (currentPage >= totalPages - halfVisible) {
startPage = Math.max(2, totalPages - maxVisiblePages + 2);
}
// 添加前省略号(如果需要)
if (startPage > 2) {
html += ``;
}
// 添加中间页码
for (let i = startPage; i <= endPage; i++) {
if (i === currentPage) {
html += ``;
} else {
html += ``;
}
}
// 添加后省略号(如果需要)
if (endPage < totalPages - 1) {
html += ``;
}
// 总是显示最后一页(如果总页数大于1)
if (totalPages > 1) {
if (currentPage === totalPages) {
html += ``;
} else {
html += ``;
}
}
// 下一页按钮
if (currentPage < totalPages) {
html += ``;
}
// 添加页面跳转输入框
html += `
`;
pagination.innerHTML = html;
}
// 页面跳转功能
jumpToPage() {
const input = document.getElementById('page-jump-input');
const page = parseInt(input.value);
// 从页面跳转输入框的max属性获取总页数
const maxPage = parseInt(input.getAttribute('max')) || 1;
if (page && page >= 1 && page <= maxPage) {
this.loadStockData(page);
input.value = '';
} else {
this.showError('请输入有效的页码(1-' + maxPage + ')');
}
}
// 填充股票选择器
populateStockSelectors(stocks) {
const selectors = [
document.getElementById('stock-selector'),
document.getElementById('financial-stock-selector')
];
selectors.forEach(selector => {
// 清空现有选项(保留第一个选项)
while (selector.children.length > 1) {
selector.removeChild(selector.lastChild);
}
// 添加股票选项
stocks.forEach(stock => {
const option = document.createElement('option');
option.value = stock.code;
option.textContent = `${stock.code} - ${stock.name}`;
selector.appendChild(option);
});
});
}
// 搜索股票
async searchStocks() {
const query = document.getElementById('stock-search').value.trim();
if (!query) {
await this.loadStockData();
return;
}
try {
this.showLoading();
const response = await this.apiCall(`/api/stocks/search?q=${encodeURIComponent(query)}`);
if (response.success) {
this.renderStockTable(response.data);
document.getElementById('stock-pagination').innerHTML = '';
}
} catch (error) {
console.error('搜索股票失败:', error);
this.showError('搜索股票失败');
} finally {
this.hideLoading();
}
}
// 加载K线图表
async loadKlineChart() {
if (!this.currentStock) return;
try {
this.showLoading();
const period = document.getElementById('period-selector').value;
const response = await this.apiCall(`/api/kline/${this.currentStock}?period=${period}`);
if (response.success) {
this.renderKlineChart(response.data);
}
} catch (error) {
console.error('加载K线数据失败:', error);
this.showError('加载K线数据失败');
} finally {
this.hideLoading();
}
}
// 渲染K线图表
renderKlineChart(klineData) {
const ctx = document.getElementById('kline-chart-canvas').getContext('2d');
// 销毁现有图表
if (this.klineChart) {
this.klineChart.destroy();
}
const dates = klineData.map(item => item.date);
const prices = klineData.map(item => item.close);
this.klineChart = new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: '收盘价',
data: prices,
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '股票价格走势图'
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}
// 加载财务数据
async loadFinancialData(stockCode = null) {
try {
this.showLoading();
if (!stockCode) {
stockCode = document.getElementById('financial-stock-selector').value;
}
if (!stockCode) return;
const period = document.getElementById('report-period').value;
const year = document.getElementById('report-year').value;
const response = await this.apiCall(`/api/financial/${stockCode}?year=${year}&period=${period}`);
if (response.success) {
this.renderFinancialTable(response.data);
}
} catch (error) {
console.error('加载财务数据失败:', error);
this.showError('加载财务数据失败');
} finally {
this.hideLoading();
}
}
// 渲染财务数据表格
renderFinancialTable(financialData) {
const tbody = document.getElementById('financial-table-body');
tbody.innerHTML = '';
if (!financialData || Object.keys(financialData).length === 0) {
tbody.innerHTML = '| 暂无财务数据 |
';
return;
}
const financialItems = [
{ key: 'revenue', label: '营业收入', unit: '万元' },
{ key: 'net_profit', label: '净利润', unit: '万元' },
{ key: 'total_assets', label: '总资产', unit: '万元' },
{ key: 'total_liabilities', label: '总负债', unit: '万元' },
{ key: 'eps', label: '每股收益', unit: '元' },
{ key: 'roe', label: '净资产收益率', unit: '%' }
];
financialItems.forEach(item => {
if (financialData[item.key] !== undefined) {
const row = document.createElement('tr');
row.innerHTML = `
${item.label} |
${this.formatNumber(financialData[item.key])} |
${item.unit} |
${this.calculateChange(financialData[item.key])} |
`;
tbody.appendChild(row);
}
});
}
// 加载系统日志
async loadSystemLogs() {
try {
this.showLoading();
const level = document.getElementById('log-level-filter').value;
const date = document.getElementById('log-date-filter').value;
let url = '/api/system/logs';
const params = [];
if (level) params.push(`level=${level}`);
if (date) params.push(`date=${date}`);
if (params.length > 0) {
url += '?' + params.join('&');
}
const response = await this.apiCall(url);
if (response.success) {
this.renderSystemLogs(response.data);
}
} catch (error) {
console.error('加载系统日志失败:', error);
this.showError('加载系统日志失败');
} finally {
this.hideLoading();
}
}
// 渲染系统日志
renderSystemLogs(logs) {
const logList = document.getElementById('log-list');
logList.innerHTML = '';
if (!logs || logs.length === 0) {
logList.innerHTML = '暂无系统日志
';
return;
}
logs.forEach(log => {
const logItem = document.createElement('div');
logItem.className = `log-item ${log.level.toLowerCase()}`;
logItem.innerHTML = `
${log.message}
模块: ${log.module_name} | 事件: ${log.event_type}
${log.exception_type ? ` | 异常: ${log.exception_type}` : ''}
`;
logList.appendChild(logItem);
});
}
// 查看股票详情
viewStockDetails(stockCode) {
this.showStockDetailsModal(stockCode);
}
// 显示股票详情模态框
async showStockDetailsModal(stockCode) {
try {
this.showLoading();
// 获取股票详细信息
const stockResponse = await this.apiCall(`/api/stocks/${stockCode}`);
const klineResponse = await this.apiCall(`/api/kline/${stockCode}?period=daily&limit=30`);
if (stockResponse.success && klineResponse.success) {
this.renderStockDetailsModal(stockCode, stockResponse.data, klineResponse.data);
} else {
this.showError('获取股票详情失败');
}
} catch (error) {
console.error('显示股票详情失败:', error);
this.showError('显示股票详情失败');
} finally {
this.hideLoading();
}
}
// 渲染股票详情模态框
renderStockDetailsModal(stockCode, stockData, klineData) {
// 创建模态框HTML
const modalHtml = `
基本信息
${stockData.exchange}
${this.formatDate(stockData.listing_date)}
${stockData.industry || '-'}
价格信息
${klineData && klineData.length > 0 ? `
${klineData[klineData.length - 1].close.toFixed(2)}
${this.calculateChange(klineData[klineData.length - 1].close)}
` : '
暂无价格数据
'}
`;
// 添加模态框到页面
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 渲染迷你K线图
if (klineData && klineData.length > 0) {
this.renderMiniKlineChart(klineData);
}
// 添加点击遮罩关闭功能
document.getElementById('stock-details-modal').addEventListener('click', (e) => {
if (e.target.id === 'stock-details-modal') {
this.closeStockDetailsModal();
}
});
}
// 渲染迷你K线图
renderMiniKlineChart(klineData) {
const ctx = document.getElementById('mini-kline-chart').getContext('2d');
const dates = klineData.map(item => item.date);
const prices = klineData.map(item => item.close);
new Chart(ctx, {
type: 'line',
data: {
labels: dates,
datasets: [{
label: '收盘价',
data: prices,
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
fill: true,
tension: 0.1,
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: false }
},
scales: {
y: { display: false },
x: { display: false }
}
}
});
}
// 关闭股票详情模态框
closeStockDetailsModal() {
const modal = document.getElementById('stock-details-modal');
if (modal) {
modal.remove();
}
}
// API调用封装
async apiCall(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('API调用失败:', error);
// 如果后端API不可用,回退到模拟数据
return this.getMockData(url);
}
}
// 获取模拟数据
getMockData(url) {
if (url.includes('/api/system/overview')) {
return {
success: true,
stock_count: 12595,
kline_count: 440,
financial_count: 50,
log_count: 4
};
}
if (url.includes('/api/stocks')) {
// 模拟股票数据
const mockStocks = [
{ code: '000001', name: '平安银行', exchange: 'SZ', listing_date: '1991-04-03', industry: '银行' },
{ code: '000002', name: '万科A', exchange: 'SZ', listing_date: '1991-01-29', industry: '房地产' },
{ code: '600000', name: '浦发银行', exchange: 'SH', listing_date: '1999-11-10', industry: '银行' },
{ code: '600036', name: '招商银行', exchange: 'SH', listing_date: '2002-04-09', industry: '银行' },
{ code: '601318', name: '中国平安', exchange: 'SH', listing_date: '2007-03-01', industry: '保险' }
];
return {
success: true,
data: mockStocks,
total: 12595
};
}
if (url.includes('/api/kline/')) {
// 模拟K线数据
const dates = [];
const prices = [];
const basePrice = 10;
for (let i = 30; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
dates.push(date.toISOString().split('T')[0]);
// 模拟价格波动
const price = basePrice + Math.random() * 5;
prices.push({
date: date.toISOString().split('T')[0],
open: price - 0.5,
high: price + 0.8,
low: price - 0.8,
close: price,
volume: Math.floor(Math.random() * 1000000)
});
}
return {
success: true,
data: prices
};
}
if (url.includes('/api/financial/')) {
// 模拟财务数据
return {
success: true,
data: {
revenue: 500000,
net_profit: 80000,
total_assets: 2000000,
total_liabilities: 1200000,
eps: 1.5,
roe: 15.2
}
};
}
if (url.includes('/api/system/logs')) {
// 模拟系统日志
return {
success: true,
data: [
{
id: 1,
timestamp: new Date().toISOString(),
level: 'INFO',
module_name: 'System',
event_type: 'STARTUP',
message: '系统启动成功',
exception_type: null
},
{
id: 2,
timestamp: new Date(Date.now() - 3600000).toISOString(),
level: 'INFO',
module_name: 'DataCollector',
event_type: 'DATA_COLLECTION',
message: '开始采集股票数据',
exception_type: null
},
{
id: 3,
timestamp: new Date(Date.now() - 1800000).toISOString(),
level: 'ERROR',
module_name: 'Database',
event_type: 'CONNECTION_ERROR',
message: '数据库连接失败',
exception_type: 'ConnectionError'
},
{
id: 4,
timestamp: new Date(Date.now() - 900000).toISOString(),
level: 'WARNING',
module_name: 'DataProcessor',
event_type: 'DATA_FORMAT',
message: '数据格式异常,已自动修复',
exception_type: 'FormatError'
}
]
};
}
return { success: false, message: 'API endpoint not found' };
}
// 工具函数
formatNumber(num) {
if (num === null || num === undefined) return '-';
return new Intl.NumberFormat('zh-CN').format(num);
}
formatDate(dateString) {
if (!dateString) return '-';
return new Date(dateString).toLocaleDateString('zh-CN');
}
formatDateTime(dateString) {
if (!dateString) return '-';
return new Date(dateString).toLocaleString('zh-CN');
}
calculateChange(value) {
const change = (Math.random() - 0.5) * 20;
const sign = change >= 0 ? '+' : '';
const color = change >= 0 ? '#27ae60' : '#e74c3c';
return `${sign}${change.toFixed(2)}%`;
}
showError(message) {
alert(`错误: ${message}`);
}
}
// 全局应用实例
const app = new StockDataApp();
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
console.log('股票数据展示系统已加载');
});