增加数据-人民币汇率
This commit is contained in:
parent
d232591a38
commit
306af38fdc
145
components/TimeRangePicker.js
Normal file
145
components/TimeRangePicker.js
Normal 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
|
||||||
@ -98,8 +98,11 @@ export default async function handler(req, res) {
|
|||||||
error: error.message
|
error: error.message
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
if (pool) {
|
// 不关闭连接池,保持连接池打开以提高性能
|
||||||
await pool.end();
|
// 如果需要关闭连接池,应该在关闭后将 pool 变量设置为 null
|
||||||
}
|
// if (pool) {
|
||||||
|
// await pool.end();
|
||||||
|
// pool = null;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
|||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import Navbar from '../components/Navbar'
|
import Navbar from '../components/Navbar'
|
||||||
import Footer from '../components/Footer'
|
import Footer from '../components/Footer'
|
||||||
|
import TimeRangePicker from '../components/TimeRangePicker'
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
|
||||||
|
|
||||||
const chinaBondData = [
|
const chinaBondData = [
|
||||||
@ -162,18 +163,39 @@ export default function Data() {
|
|||||||
const [exchangeRateData, setExchangeRateData] = useState([])
|
const [exchangeRateData, setExchangeRateData] = useState([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
const [timeRange, setTimeRange] = useState('1m') // 1m: 近一个月, 3m: 近三个月, 1y: 近一年, custom: 自定义
|
||||||
|
const [startDate, setStartDate] = useState('')
|
||||||
|
const [endDate, setEndDate] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedIndicator === 'central-bank-usd-cny-rate') {
|
if (selectedIndicator === 'central-bank-usd-cny-rate') {
|
||||||
fetchExchangeRateData()
|
fetchExchangeRateData()
|
||||||
}
|
}
|
||||||
}, [selectedIndicator])
|
}, [selectedIndicator, timeRange, startDate, endDate])
|
||||||
|
|
||||||
|
const handleTimeRangeChange = (range, start, end) => {
|
||||||
|
setTimeRange(range)
|
||||||
|
setStartDate(start)
|
||||||
|
setEndDate(end)
|
||||||
|
}
|
||||||
|
|
||||||
const fetchExchangeRateData = async () => {
|
const fetchExchangeRateData = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
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()
|
const data = await response.json()
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setExchangeRateData(data.data)
|
setExchangeRateData(data.data)
|
||||||
@ -566,6 +588,13 @@ export default function Data() {
|
|||||||
<div className="error">{error}</div>
|
<div className="error">{error}</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<div className="chart-header">
|
||||||
|
<TimeRangePicker
|
||||||
|
value={timeRange}
|
||||||
|
onChange={handleTimeRangeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<ResponsiveContainer width="100%" height={500}>
|
<ResponsiveContainer width="100%" height={500}>
|
||||||
<LineChart data={exchangeRateData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
<LineChart data={exchangeRateData} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
|
||||||
|
|||||||
@ -493,3 +493,154 @@ main {
|
|||||||
margin-bottom: 1rem;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user