增加数据-人民币汇率

This commit is contained in:
cheney 2026-04-25 19:48:19 +08:00
parent d232591a38
commit 306af38fdc
4 changed files with 333 additions and 5 deletions

View File

@ -0,0 +1,145 @@
import { useState, useEffect } from 'react'
const TimeRangePicker = ({
value = '1m',
onChange,
className = ''
}) => {
const [timeRange, setTimeRange] = useState(value)
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [showDropdown, setShowDropdown] = useState(false)
// 计算日期范围
const calculateDateRange = (range) => {
const end = new Date()
const start = new Date()
if (range === '1m') {
start.setMonth(start.getMonth() - 1)
} else if (range === '3m') {
start.setMonth(start.getMonth() - 3)
} else if (range === '1y') {
start.setFullYear(start.getFullYear() - 1)
}
const formatDate = (date) => {
return date.toISOString().split('T')[0]
}
return {
start: formatDate(start),
end: formatDate(end)
}
}
// 初始化时设置日期范围
useEffect(() => {
const range = calculateDateRange(value)
setStartDate(range.start)
setEndDate(range.end)
}, [value])
// 处理时间范围变化
const handleTimeRangeChange = (range) => {
const dateRange = calculateDateRange(range)
setStartDate(dateRange.start)
setEndDate(dateRange.end)
setTimeRange(range)
if (onChange) {
onChange(range, dateRange.start, dateRange.end)
}
setShowDropdown(false)
}
// 处理自定义日期范围
const handleCustomRangeApply = () => {
if (startDate && endDate) {
setTimeRange('custom')
if (onChange) {
onChange('custom', startDate, endDate)
}
setShowDropdown(false)
}
}
// 处理开始日期变化
const handleStartDateChange = (e) => {
setStartDate(e.target.value)
}
// 处理结束日期变化
const handleEndDateChange = (e) => {
setEndDate(e.target.value)
}
return (
<div className={`time-range-picker ${className}`}>
<button
className="time-range-btn"
onClick={() => setShowDropdown(!showDropdown)}
>
{timeRange === '1m' && '近一个月'}
{timeRange === '3m' && '近三个月'}
{timeRange === '1y' && '近一年'}
{timeRange === 'custom' && startDate && endDate && `${startDate}${endDate}`}
{timeRange === 'custom' && (!startDate || !endDate) && '自定义'}
<span className="dropdown-arrow"></span>
</button>
{showDropdown && (
<div className="time-range-dropdown">
<div className="dropdown-section">
<h4 className="dropdown-title">自定义范围</h4>
<div className="custom-date-range">
<input
type="date"
value={startDate}
onChange={handleStartDateChange}
max={endDate || new Date().toISOString().split('T')[0]}
/>
<span></span>
<input
type="date"
value={endDate}
onChange={handleEndDateChange}
min={startDate}
max={new Date().toISOString().split('T')[0]}
/>
<button
className="apply-btn"
onClick={handleCustomRangeApply}
>
确定
</button>
</div>
</div>
<div className="dropdown-section">
<h4 className="dropdown-title">快捷选项</h4>
<ul className="dropdown-options">
<li
className={`dropdown-option ${timeRange === '1m' ? 'active' : ''}`}
onClick={() => handleTimeRangeChange('1m')}
>
近一个月
</li>
<li
className={`dropdown-option ${timeRange === '3m' ? 'active' : ''}`}
onClick={() => handleTimeRangeChange('3m')}
>
近三个月
</li>
<li
className={`dropdown-option ${timeRange === '1y' ? 'active' : ''}`}
onClick={() => handleTimeRangeChange('1y')}
>
近一年
</li>
</ul>
</div>
</div>
)}
</div>
)
}
export default TimeRangePicker

View File

@ -98,8 +98,11 @@ export default async function handler(req, res) {
error: error.message
});
} finally {
if (pool) {
await pool.end();
}
// 不关闭连接池,保持连接池打开以提高性能
// 如果需要关闭连接池,应该在关闭后将 pool 变量设置为 null
// if (pool) {
// await pool.end();
// pool = null;
// }
}
}

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import Head from 'next/head'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import TimeRangePicker from '../components/TimeRangePicker'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
const chinaBondData = [
@ -162,18 +163,39 @@ export default function Data() {
const [exchangeRateData, setExchangeRateData] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [timeRange, setTimeRange] = useState('1m') // 1m: 近一个月, 3m: 近三个月, 1y: 近一年, custom: 自定义
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
useEffect(() => {
if (selectedIndicator === 'central-bank-usd-cny-rate') {
fetchExchangeRateData()
}
}, [selectedIndicator])
}, [selectedIndicator, timeRange, startDate, endDate])
const handleTimeRangeChange = (range, start, end) => {
setTimeRange(range)
setStartDate(start)
setEndDate(end)
}
const fetchExchangeRateData = async () => {
setLoading(true)
setError(null)
try {
const response = await fetch('/api/exchange-rate?limit=60')
let url = '/api/exchange-rate'
if (timeRange === 'custom' && startDate && endDate) {
url += `?startDate=${startDate}&endDate=${endDate}`
} else if (timeRange === '1m') {
url += '?limit=30'
} else if (timeRange === '3m') {
url += '?limit=90'
} else if (timeRange === '1y') {
url += '?limit=365'
}
const response = await fetch(url)
const data = await response.json()
if (data.success) {
setExchangeRateData(data.data)
@ -566,6 +588,13 @@ export default function Data() {
<div className="error">{error}</div>
) : (
<>
<div className="chart-header">
<TimeRangePicker
value={timeRange}
onChange={handleTimeRangeChange}
/>
</div>
<div className="chart-container">
<ResponsiveContainer width="100%" height={500}>
<LineChart data={exchangeRateData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>

View File

@ -493,3 +493,154 @@ main {
margin-bottom: 1rem;
}
/* Time range selector */
.chart-header {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.time-range-picker {
position: relative;
display: inline-block;
}
.time-range-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
min-width: 150px;
justify-content: space-between;
transition: all 0.3s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.time-range-btn:hover {
border-color: #333;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dropdown-arrow {
font-size: 12px;
color: #666;
transition: transform 0.3s ease;
}
.time-range-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 16px;
min-width: 300px;
z-index: 1000;
}
.dropdown-section {
margin-bottom: 16px;
}
.dropdown-section:last-child {
margin-bottom: 0;
}
.dropdown-title {
font-size: 12px;
font-weight: 600;
color: #999;
margin-bottom: 8px;
text-transform: uppercase;
}
.dropdown-options {
list-style: none;
padding: 0;
margin: 0;
}
.dropdown-option {
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
font-size: 14px;
}
.dropdown-option:hover {
background-color: #f0f0f0;
}
.dropdown-option.active {
background-color: #333;
color: #fff;
}
.custom-date-range {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.custom-date-range input[type="date"] {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
min-width: 120px;
}
.custom-date-range span {
font-size: 14px;
color: #666;
white-space: nowrap;
}
.apply-btn {
padding: 6px 12px;
border: 1px solid #333;
border-radius: 4px;
background-color: #333;
color: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.apply-btn:hover {
background-color: #555;
border-color: #555;
}
.apply-btn:disabled {
background-color: #f0f0f0;
color: #999;
border-color: #ddd;
cursor: not-allowed;
}
/* Loading and error states */
.loading {
text-align: center;
padding: 40px;
font-size: 16px;
color: #666;
}
.error {
text-align: center;
padding: 40px;
font-size: 16px;
color: #e53935;
}