diff --git a/server/handler/dashboard.go b/server/handler/dashboard.go index 38c86f3..05964ed 100644 --- a/server/handler/dashboard.go +++ b/server/handler/dashboard.go @@ -55,3 +55,17 @@ func (h *DashboardHandler) GetHigh100(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{"data": data}) } + +func (h *DashboardHandler) GetIndexKline(c *gin.Context) { + code := c.DefaultQuery("code", "000001") + days, _ := strconv.Atoi(c.DefaultQuery("days", "60")) + if days <= 0 || days > 365 { + days = 60 + } + data, err := h.svc.GetIndexKline(code, days) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": data}) +} diff --git a/server/model/stock.go b/server/model/stock.go index cd25f5f..4dba6bb 100644 --- a/server/model/stock.go +++ b/server/model/stock.go @@ -71,3 +71,14 @@ type ConsecutiveLimit struct { Close float64 `json:"close"` PctChange float64 `json:"pct_change"` } + +type IndexKline struct { + Date string `json:"date"` + Open float64 `json:"open"` + Close float64 `json:"close"` + High float64 `json:"high"` + Low float64 `json:"low"` + Volume float64 `json:"volume"` + Amount float64 `json:"amount"` + PctChange float64 `json:"pct_change"` +} diff --git a/server/repository/stock_repo.go b/server/repository/stock_repo.go index b2126a2..3fabc50 100644 --- a/server/repository/stock_repo.go +++ b/server/repository/stock_repo.go @@ -291,3 +291,14 @@ func (r *StockRepo) GetConsecutiveLimits() ([]model.ConsecutiveLimit, error) { return result, nil } + +func (r *StockRepo) GetIndexKline(code string, days int) ([]model.IndexKline, error) { + var result []model.IndexKline + r.db.Raw(` + SELECT date, open, close, high, low, volume, amount, pct_change + FROM index_daily + WHERE code = ? + ORDER BY date ASC + `, code).Scan(&result) + return result, nil +} diff --git a/server/router/router.go b/server/router/router.go index 1c81766..7de3343 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -16,6 +16,7 @@ func Setup(r *gin.Engine, dh *handler.DashboardHandler) { api.GET("/dashboard/daily", dh.GetDailySummary) api.GET("/dashboard/consecutive", dh.GetConsecutiveLimits) api.GET("/dashboard/high100", dh.GetHigh100) + api.GET("/dashboard/index-kline", dh.GetIndexKline) } // Serve frontend static files diff --git a/server/service/dashboard_svc.go b/server/service/dashboard_svc.go index 0883146..4410a56 100644 --- a/server/service/dashboard_svc.go +++ b/server/service/dashboard_svc.go @@ -13,12 +13,13 @@ type cacheEntry[T any] struct { } type DashboardService struct { - repo *repository.StockRepo - mu sync.RWMutex - summary *cacheEntry[*model.DashboardSummary] - daily map[int]*cacheEntry[[]model.DailySummary] - consec *cacheEntry[[]model.ConsecutiveLimit] - high100 *cacheEntry[[]model.StockRank] + repo *repository.StockRepo + mu sync.RWMutex + summary *cacheEntry[*model.DashboardSummary] + daily map[int]*cacheEntry[[]model.DailySummary] + consec *cacheEntry[[]model.ConsecutiveLimit] + high100 *cacheEntry[[]model.StockRank] + indexKline map[string]*cacheEntry[[]model.IndexKline] } func NewDashboardService(repo *repository.StockRepo) *DashboardService { @@ -114,3 +115,27 @@ func (s *DashboardService) GetHigh100() ([]model.StockRank, error) { return data, nil } + +func (s *DashboardService) GetIndexKline(code string, days int) ([]model.IndexKline, error) { + s.mu.RLock() + if e, ok := s.indexKline[code]; ok && time.Now().Before(e.expires) { + data := e.data + s.mu.RUnlock() + return data, nil + } + s.mu.RUnlock() + + data, err := s.repo.GetIndexKline(code, days) + if err != nil { + return nil, err + } + + s.mu.Lock() + if s.indexKline == nil { + s.indexKline = make(map[string]*cacheEntry[[]model.IndexKline]) + } + s.indexKline[code] = &cacheEntry[[]model.IndexKline]{data: data, expires: time.Now().Add(cacheTTL)} + s.mu.Unlock() + + return data, nil +} diff --git a/web/src/App.tsx b/web/src/App.tsx index f115a69..5eaf476 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'; import zhCN from 'antd/locale/zh_CN'; import { DashboardOutlined, + FundOutlined, LineChartOutlined, RiseOutlined, ThunderboltOutlined, @@ -12,11 +13,13 @@ import Dashboard from './pages/Dashboard'; import TrendPage from './pages/TrendPage'; import ConsecutivePage from './pages/ConsecutivePage'; import High100Page from './pages/High100Page'; +import IndexPage from './pages/IndexPage'; const { Header, Content } = Layout; const menuItems = [ { key: '/', icon: , label: '市场概览' }, + { key: '/index', icon: , label: '指数行情' }, { key: '/high100', icon: , label: '百日新高' }, { key: '/consecutive', icon: , label: '连板高度' }, { key: '/trend', icon: , label: '每日趋势' }, @@ -44,6 +47,7 @@ function AppLayout() { } /> + } /> } /> } /> } /> diff --git a/web/src/api/dashboard.ts b/web/src/api/dashboard.ts index f553245..48c418d 100644 --- a/web/src/api/dashboard.ts +++ b/web/src/api/dashboard.ts @@ -65,3 +65,19 @@ export async function getHigh100(): Promise { const res = await api.get('/dashboard/high100'); return res.data.data; } + +export interface IndexKline { + date: string; + open: number; + close: number; + high: number; + low: number; + volume: number; + amount: number; + pct_change: number; +} + +export async function getIndexKline(code = '000001', days = 60): Promise { + const res = await api.get('/dashboard/index-kline', { params: { code, days } }); + return res.data.data; +}