diff --git a/components/TimeRangePicker.js b/components/TimeRangePicker.js
new file mode 100644
index 0000000..ea38ca7
--- /dev/null
+++ b/components/TimeRangePicker.js
@@ -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 (
+
+
+ {showDropdown && (
+
+
+
+
快捷选项
+
+ - handleTimeRangeChange('1m')}
+ >
+ 近一个月
+
+ - handleTimeRangeChange('3m')}
+ >
+ 近三个月
+
+ - handleTimeRangeChange('1y')}
+ >
+ 近一年
+
+
+
+
+ )}
+
+ )
+}
+
+export default TimeRangePicker
diff --git a/pages/api/exchange-rate.js b/pages/api/exchange-rate.js
index 3ecb5e1..32cba0f 100644
--- a/pages/api/exchange-rate.js
+++ b/pages/api/exchange-rate.js
@@ -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;
+ // }
}
}
diff --git a/pages/data.js b/pages/data.js
index c169462..2d08de7 100644
--- a/pages/data.js
+++ b/pages/data.js
@@ -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() {
{error}
) : (
<>
+
+
+
+
diff --git a/styles/globals.css b/styles/globals.css
index 27c607c..201e9fb 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -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;
+}
+