diff --git a/server/main.go b/server/main.go index e41b3e9..aaa6bc0 100644 --- a/server/main.go +++ b/server/main.go @@ -28,18 +28,24 @@ func main() { } fmt.Println("Database connected successfully") - // 异步创建常用索引,避免首次查询慢 + // 异步创建常用索引,已存在则跳过 go func() { time.Sleep(3 * time.Second) - indexes := []string{ - "ALTER TABLE stock_daily ADD INDEX idx_sd_date (date)", - "ALTER TABLE stock_daily ADD INDEX idx_sd_code_date (code, date)", - "ALTER TABLE stock_daily ADD INDEX idx_sd_date_pct (date, pct_change)", - "ALTER TABLE stock_daily ADD INDEX idx_sd_code_date_close (code, date, close)", + indexes := []struct { + name string + sql string + }{ + {"idx_sd_date", "ALTER TABLE stock_daily ADD INDEX idx_sd_date (date)"}, + {"idx_sd_code_date", "ALTER TABLE stock_daily ADD INDEX idx_sd_code_date (code, date)"}, + {"idx_sd_date_pct", "ALTER TABLE stock_daily ADD INDEX idx_sd_date_pct (date, pct_change)"}, } for _, idx := range indexes { - if err := db.Exec(idx).Error; err == nil { - fmt.Printf("index created: %s\n", idx) + var cnt int64 + db.Raw("SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'stock_daily' AND index_name = ?", idx.name).Scan(&cnt) + if cnt == 0 { + if err := db.Exec(idx.sql).Error; err == nil { + fmt.Printf("index created: %s\n", idx.name) + } } } }() diff --git a/web/src/components/IndexKlineChart.tsx b/web/src/components/IndexKlineChart.tsx index 25cf33e..f32e57b 100644 --- a/web/src/components/IndexKlineChart.tsx +++ b/web/src/components/IndexKlineChart.tsx @@ -120,5 +120,5 @@ export default function IndexKlineChart({ data, title = '上证指数' }: Props) return () => window.removeEventListener('resize', handleResize); }, [data, title]); - return
; + return
; } diff --git a/web/src/components/OverlayKlineChart.tsx b/web/src/components/OverlayKlineChart.tsx index b169a3b..791acbe 100644 --- a/web/src/components/OverlayKlineChart.tsx +++ b/web/src/components/OverlayKlineChart.tsx @@ -81,6 +81,14 @@ export default function OverlayKlineChart({ series }: Props) { } const baseDates = [...dateSet].sort(); + // 日期交集为空时无法绘制,清空图表退出 + if (baseDates.length === 0) { + if (chartRef.current) { + chartRef.current.clear(); + } + return; + } + // ── 2. 建立日期→数据的快速查找映射 ── const dateMaps = series.map((s) => new Map(s.data.map((d) => [d.date.slice(0, 10), d]))); diff --git a/web/src/pages/ConsecutivePage.tsx b/web/src/pages/ConsecutivePage.tsx index 887dff7..fe4f670 100644 --- a/web/src/pages/ConsecutivePage.tsx +++ b/web/src/pages/ConsecutivePage.tsx @@ -64,11 +64,7 @@ export default function ConsecutivePage() { const maxStreak = data.length > 0 ? data[0].streak : 0; return ( -
- - 连板高度 - - +
今日涨停 <span style={{ color: '#cf1322' }}>{data.length}</span> 只, diff --git a/web/src/pages/Dashboard.tsx b/web/src/pages/Dashboard.tsx index 12adba8..311f2ea 100644 --- a/web/src/pages/Dashboard.tsx +++ b/web/src/pages/Dashboard.tsx @@ -1,13 +1,11 @@ import { useEffect, useState } from 'react'; -import { Card, Spin, Table, Tag, Typography } from 'antd'; +import { Card, Spin, Table, Tag } from 'antd'; import { getDashboardSummary, type DashboardSummary, } from '../api/dashboard'; import StatCards from '../components/StatCard'; -const { Title } = Typography; - const columns = [ { title: '代码', dataIndex: 'code', key: 'code', width: 80 }, { title: '名称', dataIndex: 'name', key: 'name', width: 100 }, @@ -55,11 +53,7 @@ export default function Dashboard() { if (!summary) return <div>加载失败</div>; return ( - <div style={{ maxWidth: 1200, margin: '0 auto' }}> - <Title level={2} style={{ textAlign: 'center', marginBottom: 32 }}> - A股市场概览 - - +
diff --git a/web/src/pages/High100Page.tsx b/web/src/pages/High100Page.tsx index 2f1f906..bdad5fc 100644 --- a/web/src/pages/High100Page.tsx +++ b/web/src/pages/High100Page.tsx @@ -53,11 +53,7 @@ export default function High100Page() { const limitUp = data.filter((s) => s.pct_change >= 9.5).length; return ( -
- - 百日新高 - - +
今日创150日新高 <span style={{ color: '#cf1322', fontSize: 28 }}>{data.length}</span> 只 diff --git a/web/src/pages/IndexPage.tsx b/web/src/pages/IndexPage.tsx index 419d7ff..b5ff93d 100644 --- a/web/src/pages/IndexPage.tsx +++ b/web/src/pages/IndexPage.tsx @@ -1,13 +1,13 @@ /** * 指数行情页面 - * 左侧显示指数列表(上证指数、深证成指等),右侧显示选中指数的 K 线图 + * 左侧显示指数列表,右侧显示选中指数的 K 线图,无左右留白 */ import { useEffect, useState } from 'react'; import { Card, List, Spin, Typography } from 'antd'; import { getIndexKline, type IndexKline } from '../api/dashboard'; import IndexKlineChart from '../components/IndexKlineChart'; -const { Title, Text } = Typography; +const { Text } = Typography; /** 支持的指数列表 */ const INDICES = [ @@ -35,58 +35,53 @@ export default function IndexPage() { const latest = kline.length > 0 ? kline[kline.length - 1] : null; return ( - <div style={{ maxWidth: 1200, margin: '0 auto' }}> - <Title level={2} style={{ textAlign: 'center', marginBottom: 24 }}> - 指数行情 - - -
- {/* 左侧指数列表 */} - - ( - setSelected(item.code)} - style={{ - cursor: 'pointer', - padding: '8px 12px', - background: item.code === selected ? '#e6f7ff' : undefined, - borderRadius: 4, - }} - > - {item.name} - - )} - /> - - - {/* 右侧 K 线图 */} - i.code === selected)?.name} +
+ {/* 左侧指数列表 */} + + - {/* 最新收盘价和涨跌幅 */} - {latest && ( -
- {latest.close.toFixed(2)} - = 0 ? '#cf1322' : '#3f8600' }} - > - {latest.pct_change >= 0 ? '+' : ''}{latest.pct_change.toFixed(2)}% - -
+ dataSource={INDICES} + renderItem={(item) => ( + setSelected(item.code)} + style={{ + cursor: 'pointer', + padding: '8px 12px', + background: item.code === selected ? '#e6f7ff' : undefined, + borderRadius: 4, + }} + > + {item.name} + )} - {loading ? ( -
- -
- ) : ( - - )} -
-
+ /> +
+ + {/* 右侧 K 线图 */} + i.code === selected)?.name} + size="small" + style={{ flex: 1, overflow: 'hidden' }} + > + {/* 最新收盘价和涨跌幅 */} + {latest && ( +
+ {latest.close.toFixed(2)} + = 0 ? '#cf1322' : '#3f8600' }} + > + {latest.pct_change >= 0 ? '+' : ''}{latest.pct_change.toFixed(2)}% + +
+ )} + {loading ? ( +
+ +
+ ) : ( + + )} +
); } diff --git a/web/src/pages/OverlayPage.tsx b/web/src/pages/OverlayPage.tsx index af3a5f4..f3e9ce8 100644 --- a/web/src/pages/OverlayPage.tsx +++ b/web/src/pages/OverlayPage.tsx @@ -39,7 +39,7 @@ export default function OverlayPage() { try { const res = await getStockKline(trimmed); - if (!res.data || res.data.length === 0) { + if (!res || !res.data || res.data.length === 0) { // 无数据时移除该条目 setStocks((prev) => prev.filter((s) => s.code !== trimmed)); message.error(trimmed + ' 无数据'); diff --git a/web/src/pages/TrendPage.tsx b/web/src/pages/TrendPage.tsx index 988c781..18e0188 100644 --- a/web/src/pages/TrendPage.tsx +++ b/web/src/pages/TrendPage.tsx @@ -1,9 +1,8 @@ import { useEffect, useState } from 'react'; -import { Card, Segmented, Spin, Typography } from 'antd'; +import { Card, Segmented, Spin } from 'antd'; import { getDailySummary, type DailySummary } from '../api/dashboard'; import DailyChart from '../components/DailyChart'; -const { Title } = Typography; export default function TrendPage() { const [daily, setDaily] = useState([]); @@ -18,10 +17,7 @@ export default function TrendPage() { }, [days]); return ( -
- - 每日趋势 - +