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;
+}