812 lines
22 KiB
JavaScript
812 lines
22 KiB
JavaScript
// 股票数据展示系统前端应用
|
||
// 主应用入口文件 - 更新于 $(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 = `
|
||
<td>${stock.code}</td>
|
||
<td>${stock.name}</td>
|
||
<td>${stock.exchange}</td>
|
||
<td>${this.formatDate(stock.listing_date)}</td>
|
||
<td>${stock.industry || '-'}</td>
|
||
<td>
|
||
<button class="btn btn-primary" onclick="app.viewStockDetails('${stock.code}')">
|
||
查看详情
|
||
</button>
|
||
</td>
|
||
`;
|
||
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 += `<button onclick="app.loadStockData(${currentPage - 1})">上一页</button>`;
|
||
}
|
||
|
||
// 页码按钮 - 智能显示头尾和当前页附近的页码
|
||
const maxVisiblePages = 7; // 最多显示7个页码按钮
|
||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||
|
||
// 总是显示第一页
|
||
if (currentPage === 1) {
|
||
html += `<button class="active">1</button>`;
|
||
} else {
|
||
html += `<button onclick="app.loadStockData(1)">1</button>`;
|
||
}
|
||
|
||
// 计算起始和结束页码
|
||
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 += `<button class="ellipsis" disabled>...</button>`;
|
||
}
|
||
|
||
// 添加中间页码
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
if (i === currentPage) {
|
||
html += `<button class="active">${i}</button>`;
|
||
} else {
|
||
html += `<button onclick="app.loadStockData(${i})">${i}</button>`;
|
||
}
|
||
}
|
||
|
||
// 添加后省略号(如果需要)
|
||
if (endPage < totalPages - 1) {
|
||
html += `<button class="ellipsis" disabled>...</button>`;
|
||
}
|
||
|
||
// 总是显示最后一页(如果总页数大于1)
|
||
if (totalPages > 1) {
|
||
if (currentPage === totalPages) {
|
||
html += `<button class="active">${totalPages}</button>`;
|
||
} else {
|
||
html += `<button onclick="app.loadStockData(${totalPages})">${totalPages}</button>`;
|
||
}
|
||
}
|
||
|
||
// 下一页按钮
|
||
if (currentPage < totalPages) {
|
||
html += `<button onclick="app.loadStockData(${currentPage + 1})">下一页</button>`;
|
||
}
|
||
|
||
// 添加页面跳转输入框
|
||
html += `
|
||
<div class="page-jump">
|
||
<input type="number" id="page-jump-input" min="1" max="${totalPages}" placeholder="页码">
|
||
<button onclick="app.jumpToPage()">跳转</button>
|
||
</div>
|
||
`;
|
||
|
||
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 = '<tr><td colspan="4" style="text-align: center;">暂无财务数据</td></tr>';
|
||
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 = `
|
||
<td>${item.label}</td>
|
||
<td>${this.formatNumber(financialData[item.key])}</td>
|
||
<td>${item.unit}</td>
|
||
<td>${this.calculateChange(financialData[item.key])}</td>
|
||
`;
|
||
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 = '<div class="log-item">暂无系统日志</div>';
|
||
return;
|
||
}
|
||
|
||
logs.forEach(log => {
|
||
const logItem = document.createElement('div');
|
||
logItem.className = `log-item ${log.level.toLowerCase()}`;
|
||
|
||
logItem.innerHTML = `
|
||
<div class="log-header">
|
||
<span class="log-level ${log.level.toLowerCase()}">${log.level}</span>
|
||
<span class="log-time">${this.formatDateTime(log.timestamp)}</span>
|
||
</div>
|
||
<div class="log-message">${log.message}</div>
|
||
<div class="log-details">
|
||
模块: ${log.module_name} | 事件: ${log.event_type}
|
||
${log.exception_type ? ` | 异常: ${log.exception_type}` : ''}
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="modal-overlay show" id="stock-details-modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title">${stockData.name} (${stockData.code})</h2>
|
||
<button class="modal-close" onclick="app.closeStockDetailsModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="stock-basic-info">
|
||
<h3>基本信息</h3>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<label>交易所:</label>
|
||
<span>${stockData.exchange}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<label>上市日期:</label>
|
||
<span>${this.formatDate(stockData.listing_date)}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<label>行业分类:</label>
|
||
<span>${stockData.industry || '-'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stock-price-info">
|
||
<h3>价格信息</h3>
|
||
${klineData && klineData.length > 0 ? `
|
||
<div class="price-grid">
|
||
<div class="price-item">
|
||
<label>最新价格:</label>
|
||
<span class="price">${klineData[klineData.length - 1].close.toFixed(2)}</span>
|
||
</div>
|
||
<div class="price-item">
|
||
<label>涨跌幅:</label>
|
||
<span class="change">${this.calculateChange(klineData[klineData.length - 1].close)}</span>
|
||
</div>
|
||
</div>
|
||
` : '<p>暂无价格数据</p>'}
|
||
</div>
|
||
|
||
<div class="stock-chart">
|
||
<h3>近期走势</h3>
|
||
<div class="mini-chart-container">
|
||
<canvas id="mini-kline-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 添加模态框到页面
|
||
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 `<span style="color: ${color}">${sign}${change.toFixed(2)}%</span>`;
|
||
}
|
||
|
||
showError(message) {
|
||
alert(`错误: ${message}`);
|
||
}
|
||
}
|
||
|
||
// 全局应用实例
|
||
const app = new StockDataApp();
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
console.log('股票数据展示系统已加载');
|
||
}); |