diff --git a/README.md b/README.md index 95a781a..36e44e8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - **纯前端运行**:采用 JSONP 方案直连东方财富、腾讯财经等公开接口,彻底解决跨域问题,支持在 GitHub Pages 等静态环境直接部署。 - **本地持久化**:使用 `localStorage` 存储已添加的基金列表及配置信息,刷新不丢失。 - **响应式设计**:完美适配 PC 与移动端。针对移动端优化了文字展示、间距及交互体验。 +- **自选功能**:支持将基金添加至“自选”列表,通过 Tab 切换展示全部基金或仅自选基金。自选状态支持持久化及同步清理。 - **可自定义频率**:支持设置自动刷新间隔(5秒 - 300秒),并提供手动刷新按钮。 ## 🛠 技术栈 diff --git a/app/globals.css b/app/globals.css index 175264b..274c9f4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -336,6 +336,72 @@ body { box-shadow: 0 10px 20px rgba(248,113,113,0.25); } +.fav-button { + padding: 4px; + margin-right: 4px; + color: var(--muted); + transition: all 0.2s ease; + background: transparent; + border: none; + width: auto; + height: auto; +} +.fav-button:hover { + color: var(--accent); +} +.fav-button.active { + color: var(--accent); +} + +.tabs { + display: flex; + gap: 8px; + padding: 4px; + background: rgba(255, 255, 255, 0.05); + border-radius: 12px; + width: fit-content; + backdrop-filter: blur(8px); +} + +@media (max-width: 640px) { + .tabs { + position: sticky; + top: 60px; /* Navbar height (12px*2 + 24px) */ + z-index: 40; + width: calc(100% + 32px); + justify-content: center; + background: rgba(15, 23, 42, 0.9); + border-radius: 0; + margin: 0 -16px 16px -16px; + padding: 10px 16px; + border-bottom: 1px solid var(--border); + backdrop-filter: blur(16px); + } +} + +.tab { + padding: 8px 20px; + border-radius: 8px; + border: none; + background: transparent; + color: var(--muted); + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.tab:hover { + color: var(--text); + background: rgba(255, 255, 255, 0.05); +} + +.tab.active { + background: var(--primary); + color: white; + box-shadow: 0 4px 12px rgba(34, 211, 238, 0.3); +} + .modal-overlay { position: fixed; inset: 0; diff --git a/app/page.jsx b/app/page.jsx index 116e46e..01f39bd 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -49,6 +49,14 @@ function ChevronIcon(props) { ); } +function StarIcon({ filled, ...props }) { + return ( + + + + ); +} + function Stat({ label, value, delta }) { const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : ''; return ( @@ -82,6 +90,24 @@ export default function HomePage() { // 收起/展开状态 const [collapsedCodes, setCollapsedCodes] = useState(new Set()); + // 自选状态 + const [favorites, setFavorites] = useState(new Set()); + const [currentTab, setCurrentTab] = useState('all'); + + const toggleFavorite = (code) => { + setFavorites(prev => { + const next = new Set(prev); + if (next.has(code)) { + next.delete(code); + } else { + next.add(code); + } + localStorage.setItem('favorites', JSON.stringify(Array.from(next))); + if (next.size === 0) setCurrentTab('all'); + return next; + }); + }; + const toggleCollapse = (code) => { setCollapsedCodes(prev => { const next = new Set(prev); @@ -113,6 +139,11 @@ export default function HomePage() { if (Array.isArray(savedCollapsed)) { setCollapsedCodes(new Set(savedCollapsed)); } + // 加载自选状态 + const savedFavorites = JSON.parse(localStorage.getItem('favorites') || '[]'); + if (Array.isArray(savedFavorites)) { + setFavorites(new Set(savedFavorites)); + } } catch {} }, []); @@ -319,6 +350,16 @@ export default function HomePage() { localStorage.setItem('collapsedCodes', JSON.stringify(Array.from(nextSet))); return nextSet; }); + + // 同步删除自选状态 + setFavorites(prev => { + if (!prev.has(removeCode)) return prev; + const nextSet = new Set(prev); + nextSet.delete(removeCode); + localStorage.setItem('favorites', JSON.stringify(Array.from(nextSet))); + if (nextSet.size === 0) setCurrentTab('all'); + return nextSet; + }); }; const manualRefresh = async () => { @@ -404,15 +445,44 @@ export default function HomePage() {
+ {funds.length > 0 && favorites.size > 0 && ( +
+ + +
+ )} + {funds.length === 0 ? (
尚未添加基金
) : (
- {funds.map((f) => ( + {funds + .filter(f => currentTab === 'all' || favorites.has(f.code)) + .map((f) => (
+ {f.name} #{f.code}