diff --git a/app/assets/weChatGroup.jpg b/app/assets/weChatGroup.jpg index aad5db9..7f9ac1b 100644 Binary files a/app/assets/weChatGroup.jpg and b/app/assets/weChatGroup.jpg differ diff --git a/app/components/FundTrendChart.jsx b/app/components/FundTrendChart.jsx index 1072d87..aa96343 100644 --- a/app/components/FundTrendChart.jsx +++ b/app/components/FundTrendChart.jsx @@ -85,10 +85,11 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Red for up, Green for down (CN market style) // Hardcoded hex values from globals.css for Chart.js - const upColor = '#f87171'; // --danger + const upColor = '#f87171'; // --danger,与折线图红色一致 const downColor = '#34d399'; // --success const lineColor = change >= 0 ? upColor : downColor; - + const primaryColor = typeof document !== 'undefined' ? (getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee') : '#22d3ee'; + const chartData = useMemo(() => { // Calculate percentage change based on the first data point const firstValue = data.length > 0 ? data[0].value : 1; @@ -141,7 +142,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans data: buyPoints, borderColor: '#ffffff', borderWidth: 1, - backgroundColor: '#ef4444', + backgroundColor: primaryColor, pointStyle: 'circle', pointRadius: 2.5, pointHoverRadius: 4, @@ -154,7 +155,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans data: sellPoints, borderColor: '#ffffff', borderWidth: 1, - backgroundColor: '#22c55e', + backgroundColor: upColor, pointStyle: 'circle', pointRadius: 2.5, pointHoverRadius: 4, @@ -163,7 +164,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans } ] }; - }, [data, lineColor, transactions]); + }, [data, lineColor, transactions, primaryColor]); const options = useMemo(() => { return { @@ -268,13 +269,13 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans sellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); } const isCollision = (firstBuyIndex === sellIndex); - drawPointLabel(1, firstBuyIndex, '买入', '#ef4444', '#ffffff', isCollision ? -20 : 0); + drawPointLabel(1, firstBuyIndex, '买入', primaryColor, '#ffffff', isCollision ? -20 : 0); } } if (datasets[2] && datasets[2].data) { const firstSellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); if (firstSellIndex !== -1) { - drawPointLabel(2, firstSellIndex, '卖出', '#22c55e'); + drawPointLabel(2, firstSellIndex, '卖出', '#f87171'); } } @@ -357,8 +358,8 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans if (dsIndex > 0 && datasets[dsIndex]) { const label = datasets[dsIndex].label; // Determine background color based on dataset index - // 1 = Buy (Red), 2 = Sell (Green) - const bgColor = dsIndex === 1 ? '#ef4444' : '#22c55e'; + // 1 = Buy (主题色), 2 = Sell (与折线图红色一致) + const bgColor = dsIndex === 1 ? primaryColor : '#f87171'; // If collision, offset Buy label upwards let yOffset = 0; diff --git a/app/components/HoldingActionModal.jsx b/app/components/HoldingActionModal.jsx index 2f5f14d..9d5105e 100644 --- a/app/components/HoldingActionModal.jsx +++ b/app/components/HoldingActionModal.jsx @@ -47,7 +47,7 @@ export default function HoldingActionModal({ fund, onClose, onAction, hasHistory title="查看交易记录" > 📜 - 记录 + 交易记录 )} diff --git a/app/components/TransactionHistoryModal.jsx b/app/components/TransactionHistoryModal.jsx index 01f919c..012b0d7 100644 --- a/app/components/TransactionHistoryModal.jsx +++ b/app/components/TransactionHistoryModal.jsx @@ -90,7 +90,7 @@ export default function TransactionHistoryModal({ {pendingTransactions.map((item) => (
- + {item.type === 'buy' ? '买入' : '卖出'} {item.date} {item.isAfter3pm ? '(15:00后)' : ''} @@ -123,7 +123,7 @@ export default function TransactionHistoryModal({ sortedTransactions.map((item) => (
- + {item.type === 'buy' ? '买入' : '卖出'} {item.date} diff --git a/app/globals.css b/app/globals.css index 71249a6..708b0f5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -102,6 +102,9 @@ body { align-items: center; justify-content: space-between; gap: 16px; + /* PC 端固定高度,避免聚焦搜索时展开动画导致导航栏高度跳动 */ + min-height: 68px; + box-sizing: border-box; } .brand { @@ -133,6 +136,7 @@ body { .navbar-add-fund.search-focused { max-width: 800px; + min-width: 320px; flex: 1; } @@ -177,6 +181,17 @@ body { height: auto; } +/* PC 端聚焦搜索时禁止换行,避免导航栏高度变化 */ +@media (min-width: 641px) { + .navbar-add-fund.search-focused .search-input-wrapper { + flex-wrap: nowrap !important; + } + .navbar-add-fund.search-focused .navbar-input-shell { + flex-wrap: nowrap !important; + min-width: 0; + } +} + /* Mobile Search Logic */ @media (max-width: 640px) { /* Default: Search hidden, Trigger visible */ @@ -1012,6 +1027,39 @@ input[type="number"] { box-shadow: 0 0 0 3px rgba(96,165,250,0.15); cursor: default; } + +/* 刷新按钮外圈进度条 */ +.refresh-btn-wrap { + --progress: 0; + position: relative; + width: 40px; + height: 40px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} +.refresh-btn-wrap::before { + content: ''; + position: absolute; + inset: 0; + border-radius: 12px; + padding: 2px; + background: conic-gradient( + var(--accent) 0deg, + var(--accent) calc(var(--progress) * 360deg), + var(--border) calc(var(--progress) * 360deg) + ); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + pointer-events: none; +} +.refresh-btn-wrap .icon-button { + position: relative; + z-index: 1; +} @media (prefers-reduced-motion: reduce) { .spin { animation: none; diff --git a/app/page.jsx b/app/page.jsx index 18cce57..34515e6 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -278,6 +278,7 @@ export default function HomePage() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const timerRef = useRef(null); + const refreshCycleStartRef = useRef(Date.now()); const refreshingRef = useRef(false); const isLoggingOutRef = useRef(false); const isExplicitLoginRef = useRef(false); @@ -289,6 +290,8 @@ export default function HomePage() { // 全局刷新状态 const [refreshing, setRefreshing] = useState(false); + // 刷新周期进度 0~1,用于环形进度条 + const [refreshProgress, setRefreshProgress] = useState(0); // 收起/展开状态 const [collapsedCodes, setCollapsedCodes] = useState(new Set()); @@ -1760,6 +1763,7 @@ export default function HomePage() { }, [userMenuOpen]); useEffect(() => { + refreshCycleStartRef.current = Date.now(); if (timerRef.current) clearInterval(timerRef.current); timerRef.current = setInterval(() => { const codes = Array.from(new Set(funds.map((f) => f.code))); @@ -1770,6 +1774,17 @@ export default function HomePage() { }; }, [funds, refreshMs]); + // 刷新进度条:每 100ms 更新一次进度 + useEffect(() => { + if (funds.length === 0 || refreshMs <= 0) return; + const t = setInterval(() => { + const elapsed = Date.now() - refreshCycleStartRef.current; + const p = Math.min(1, elapsed / refreshMs); + setRefreshProgress(p); + }, 100); + return () => clearInterval(t); + }, [funds.length, refreshMs]); + const performSearch = async (val) => { if (!val.trim()) { setSearchResults([]); @@ -1917,6 +1932,7 @@ export default function HomePage() { } finally { refreshingRef.current = false; setRefreshing(false); + refreshCycleStartRef.current = Date.now(); try { await processPendingQueue(); }catch (e) { @@ -2945,7 +2961,7 @@ export default function HomePage() {
)} - {!isMobile && 项目Github地址 window.open("https://github.com/hzm0321/real-time-fund")} />} + 项目Github地址 window.open("https://github.com/hzm0321/real-time-fund")} /> {isMobile && ( )} -
- 刷新 - {Math.round(refreshMs / 1000)}秒 -
- + +
{/* {currentTab !== 'all' && currentTab !== 'fav' && (