优化分页组件:实现智能页码显示和页面跳转功能

This commit is contained in:
skdbj 2025-11-10 17:11:19 +08:00
parent 34be13df68
commit 99d5d53cc5
2 changed files with 566 additions and 12 deletions

View File

@ -455,4 +455,350 @@ body {
.control-panel button:hover {
background: #5a6fd8;
}
/* 股票详情模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 3000;
}
.modal-overlay.show {
display: flex;
}
.modal-content {
background: white;
border-radius: 10px;
padding: 2rem;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 1rem;
}
.modal-title {
font-size: 1.5rem;
font-weight: 600;
color: #2c3e50;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #7f8c8d;
}
.modal-close:hover {
color: #e74c3c;
}
/* 错误提示样式 */
.error-message {
background: #fdf2f2;
border: 1px solid #e74c3c;
color: #e74c3c;
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
.success-message {
background: #f0fff4;
border: 1px solid #38a169;
color: #38a169;
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
}
/* 空状态样式 */
.empty-state {
text-align: center;
padding: 3rem;
color: #7f8c8d;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
color: #bdc3c7;
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.content-section.active {
animation: fadeIn 0.5s ease-out;
}
/* 滚动条样式 */
.log-container::-webkit-scrollbar {
width: 8px;
}
.log-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.log-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.log-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 股票详情模态框样式 */
.modal-body {
max-height: 60vh;
overflow-y: auto;
}
.stock-basic-info,
.stock-price-info,
.stock-chart {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e0e0e0;
}
.stock-basic-info h3,
.stock-price-info h3,
.stock-chart h3 {
font-size: 1.2rem;
color: #2c3e50;
margin-bottom: 1rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 5px;
}
.info-item label {
font-weight: 600;
color: #7f8c8d;
}
.info-item span {
color: #2c3e50;
}
.price-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.price-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 5px;
}
.price-item label {
font-size: 0.9rem;
color: #7f8c8d;
margin-bottom: 0.5rem;
}
.price-item .price {
font-size: 1.5rem;
font-weight: 600;
color: #2c3e50;
}
/* 智能分页样式 */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin: 2rem 0;
flex-wrap: wrap;
}
.pagination button {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
background: white;
color: #333;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
min-width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.pagination button:hover:not(.active):not(.ellipsis) {
background: #f5f5f5;
border-color: #667eea;
}
.pagination button.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.pagination button.ellipsis {
background: transparent;
border: none;
cursor: default;
color: #999;
min-width: auto;
}
.pagination button.ellipsis:hover {
background: transparent;
}
/* 页面跳转样式 */
.page-jump {
display: flex;
align-items: center;
gap: 0.5rem;
margin-left: 1rem;
}
.page-jump input {
width: 80px;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}
.page-jump input:focus {
outline: none;
border-color: #667eea;
}
.page-jump button {
padding: 0.5rem 1rem;
background: #667eea;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.page-jump button:hover {
background: #5a6fd8;
}
/* 响应式分页 */
@media (max-width: 768px) {
.pagination {
gap: 0.25rem;
}
.pagination button {
padding: 0.25rem 0.5rem;
min-width: 35px;
height: 35px;
font-size: 0.9rem;
}
.page-jump {
margin-left: 0.5rem;
flex-direction: column;
gap: 0.25rem;
}
.page-jump input {
width: 60px;
font-size: 0.9rem;
}
.page-jump button {
padding: 0.25rem 0.5rem;
font-size: 0.9rem;
}
}
@media (max-width: 480px) {
.pagination {
flex-direction: column;
gap: 0.5rem;
}
.page-jump {
margin-left: 0;
flex-direction: row;
justify-content: center;
}
}
.price-item .change {
font-size: 1.2rem;
font-weight: 600;
}
.mini-chart-container {
height: 200px;
margin-top: 1rem;
}
/* 响应式设计增强 */
@media (max-width: 480px) {
.modal-content {
margin: 1rem;
padding: 1rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.price-grid {
grid-template-columns: 1fr;
}
.mini-chart-container {
height: 150px;
}
}

View File

@ -178,8 +178,38 @@ class StockDataApp {
html += `<button onclick="app.loadStockData(${currentPage - 1})">上一页</button>`;
}
// 页码按钮
for (let i = 1; i <= totalPages; i++) {
// 页码按钮 - 智能显示头尾和当前页附近的页码
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 {
@ -187,13 +217,51 @@ class StockDataApp {
}
}
// 添加后省略号(如果需要)
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) {
@ -428,20 +496,160 @@ class StockDataApp {
// 查看股票详情
viewStockDetails(stockCode) {
alert(`查看股票 ${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()">&times;</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) {
// 模拟API调用实际项目中需要连接到后端API
return new Promise((resolve) => {
setTimeout(() => {
// 模拟数据
const mockData = this.getMockData(url);
resolve(mockData);
}, 500);
});
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);
}
}
// 获取模拟数据