diff --git a/README.md b/README.md index e4da901..95a781a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## ✨ 特性 - **实时估值**:通过输入基金编号,实时获取并展示基金的单位净值、估值净值及实时涨跌幅。 -- **重仓追踪**:自动获取基金前 10 大重仓股票,并实时追踪重仓股的盘中涨跌情况。 +- **重仓追踪**:自动获取基金前 10 大重仓股票,并实时追踪重仓股的盘中涨跌情况。支持收起/展开展示。 - **纯前端运行**:采用 JSONP 方案直连东方财富、腾讯财经等公开接口,彻底解决跨域问题,支持在 GitHub Pages 等静态环境直接部署。 - **本地持久化**:使用 `localStorage` 存储已添加的基金列表及配置信息,刷新不丢失。 - **响应式设计**:完美适配 PC 与移动端。针对移动端优化了文字展示、间距及交互体验。 diff --git a/app/globals.css b/app/globals.css index 5b1285a..175264b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -39,7 +39,7 @@ body { backdrop-filter: blur(8px); } -.title { +.card .title { display: flex; align-items: center; gap: 12px; @@ -47,10 +47,6 @@ body { letter-spacing: 0.2px; } -.card .title { - flex-wrap: wrap; -} - .card .title span:first-child { white-space: normal; word-break: break-word; @@ -203,27 +199,20 @@ body { .stat { display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; + align-items: baseline; + gap: 8px; } .stat .label { - font-size: 11px; + font-size: 12px; color: var(--muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } .stat .value { - font-size: 17px; + font-size: 20px; font-weight: 700; - line-height: 1.2; - white-space: nowrap; } .stat .badge { - padding: 2px 6px; - font-size: 10px; - width: fit-content; + padding: 4px 8px; + font-size: 12px; } @media (max-width: 640px) { @@ -236,8 +225,29 @@ body { .card { padding: 12px; } + .stat { + flex-direction: column; + gap: 4px; + min-width: 0; + } + .stat .label { + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } .stat .value { font-size: 15px; + line-height: 1.2; + white-space: nowrap; + } + .stat .badge { + padding: 2px 6px; + font-size: 10px; + width: fit-content; + } + .card .title { + flex-wrap: wrap; } .item .name { max-width: 100px; @@ -379,3 +389,20 @@ body { animation: none; } } + +.loading-bar { + position: absolute; + top: 0; + left: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--primary), transparent); + width: 100%; + animation: loading 1.5s infinite; + z-index: 100; + border-radius: 16px 16px 0 0; +} + +@keyframes loading { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} diff --git a/app/page.jsx b/app/page.jsx index 32868fe..c581736 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -34,13 +34,21 @@ function RefreshIcon(props) { return ( ); } +function ChevronIcon(props) { + return ( + + ); +} + function Stat({ label, value, delta }) { const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : ''; return ( @@ -62,13 +70,30 @@ export default function HomePage() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const timerRef = useRef(null); - const [manualRefreshing, setManualRefreshing] = useState(false); // 刷新频率状态 const [refreshMs, setRefreshMs] = useState(30000); const [settingsOpen, setSettingsOpen] = useState(false); const [tempSeconds, setTempSeconds] = useState(30); + // 全局刷新状态 + const [refreshing, setRefreshing] = useState(false); + + // 收起/展开状态 + const [collapsedCodes, setCollapsedCodes] = useState(new Set()); + + const toggleCollapse = (code) => { + setCollapsedCodes(prev => { + const next = new Set(prev); + if (next.has(code)) { + next.delete(code); + } else { + next.add(code); + } + return next; + }); + }; + useEffect(() => { try { const saved = JSON.parse(localStorage.getItem('funds') || '[]'); @@ -221,6 +246,8 @@ export default function HomePage() { }; const refreshAll = async (codes) => { + if (refreshing) return; + setRefreshing(true); try { // 改用串行请求,避免全局回调 jsonpgz 并发冲突 const updated = []; @@ -241,6 +268,8 @@ export default function HomePage() { } } catch (e) { console.error(e); + } finally { + setRefreshing(false); } }; @@ -277,15 +306,10 @@ export default function HomePage() { }; const manualRefresh = async () => { - if (manualRefreshing) return; + if (refreshing) return; const codes = funds.map((f) => f.code); if (!codes.length) return; - setManualRefreshing(true); - try { - await refreshAll(codes); - } finally { - setManualRefreshing(false); - } + await refreshAll(codes); }; const saveSettings = (e) => { @@ -307,6 +331,7 @@ export default function HomePage() { return (