From 8a82f2f486bf182f3f0b5dad4c7189586b89a6b3 Mon Sep 17 00:00:00 2001 From: hzm <934585316@qq.com> Date: Mon, 9 Feb 2026 23:09:34 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=88=86=E7=BB=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=A2=9E=E5=8A=A0=E7=9C=BC=E7=9D=9B=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E7=B2=98=E6=80=A7=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/Icons.jsx | 19 ++++++++ app/globals.css | 18 +++++++- app/page.jsx | 95 +++++++++++++++++++++++++++++----------- 3 files changed, 105 insertions(+), 27 deletions(-) diff --git a/app/components/Icons.jsx b/app/components/Icons.jsx index 0699f9a..27f4c99 100644 --- a/app/components/Icons.jsx +++ b/app/components/Icons.jsx @@ -111,6 +111,25 @@ export function MailIcon(props) { ); } +export function EyeIcon(props) { + return ( + + + + + ); +} + +export function EyeOffIcon(props) { + return ( + + + + + + ); +} + export function GridIcon(props) { return ( diff --git a/app/globals.css b/app/globals.css index dd2e80b..2436836 100644 --- a/app/globals.css +++ b/app/globals.css @@ -676,6 +676,22 @@ input[type="number"] { backdrop-filter: none; padding: 0; } + + .group-summary-sticky { + position: sticky; + top: 175px; + z-index: 35; + width: calc(100% + 32px); + margin: 0 -16px 16px -16px; + padding: 8px 16px; + background: rgba(15, 23, 42, 0.9); + border-bottom: 1px solid var(--border); + backdrop-filter: blur(16px); + } + + .group-summary-sticky .group-summary-card { + margin-bottom: 0; + } } /* 禁止移动端输入框自动缩放 */ @@ -753,7 +769,7 @@ input[type="number"] { .chips { display: flex; flex-wrap: wrap; - gap: 8px; + gap: 4px; } .chip { diff --git a/app/page.jsx b/app/page.jsx index 15173cb..12a1c0c 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -9,7 +9,7 @@ import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import Announcement from "./components/Announcement"; import { DatePicker, DonateTabs, NumericInput, Stat } from "./components/Common"; -import { ChevronIcon, CloseIcon, CloudIcon, DragIcon, ExitIcon, GridIcon, ListIcon, LoginIcon, LogoutIcon, MailIcon, PlusIcon, RefreshIcon, SettingsIcon, SortIcon, StarIcon, TrashIcon, UpdateIcon, UserIcon } from "./components/Icons"; +import { ChevronIcon, CloseIcon, CloudIcon, DragIcon, ExitIcon, EyeIcon, EyeOffIcon, GridIcon, ListIcon, LoginIcon, LogoutIcon, MailIcon, PlusIcon, RefreshIcon, SettingsIcon, SortIcon, StarIcon, TrashIcon, UpdateIcon, UserIcon } from "./components/Icons"; import githubImg from "./assets/github.svg"; import weChatGroupImg from "./assets/weChatGroup.png"; import { supabase, isSupabaseConfigured } from './lib/supabase'; @@ -1728,6 +1728,7 @@ function CountUp({ value, prefix = '', suffix = '', decimals = 2, className = '' function GroupSummary({ funds, holdings, groupName, getProfit }) { const [showPercent, setShowPercent] = useState(true); + const [isMasked, setIsMasked] = useState(false); const rowRef = useRef(null); const [assetSize, setAssetSize] = useState(24); const [metricSize, setMetricSize] = useState(18); @@ -1789,13 +1790,27 @@ function GroupSummary({ funds, holdings, groupName, getProfit }) { if (!summary.hasHolding) return null; return ( -
+
-
{groupName}
+
+
{groupName}
+ +
¥ - + {isMasked ? ( + ****** + ) : ( + + )}
@@ -1805,8 +1820,14 @@ function GroupSummary({ funds, holdings, groupName, getProfit }) { className={summary.totalProfitToday > 0 ? 'up' : summary.totalProfitToday < 0 ? 'down' : ''} style={{ fontSize: '18px', fontWeight: 700, fontFamily: 'var(--font-mono)' }} > - {summary.totalProfitToday > 0 ? '+' : summary.totalProfitToday < 0 ? '-' : ''} - + {isMasked ? ( + ****** + ) : ( + <> + {summary.totalProfitToday > 0 ? '+' : summary.totalProfitToday < 0 ? '-' : ''} + + + )}
@@ -1817,12 +1838,16 @@ function GroupSummary({ funds, holdings, groupName, getProfit }) { onClick={() => setShowPercent(!showPercent)} title="点击切换金额/百分比" > - {summary.totalHoldingReturn > 0 ? '+' : summary.totalHoldingReturn < 0 ? '-' : ''} - {showPercent ? ( - + {isMasked ? ( + ****** ) : ( <> - + {summary.totalHoldingReturn > 0 ? '+' : summary.totalHoldingReturn < 0 ? '-' : ''} + {showPercent ? ( + + ) : ( + + )} )}
@@ -2387,13 +2412,12 @@ export default function HomePage() { }, []); const storageHelper = useMemo(() => { - const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades']); + const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades', 'viewMode']); const triggerSync = (key, prevValue, nextValue) => { if (keys.has(key)) { if (key === 'funds') { const prevSig = getFundCodesSignature(prevValue); const nextSig = getFundCodesSignature(nextValue); - debugger if (prevSig === nextSig) return; } if (!skipSyncRef.current) { @@ -2424,7 +2448,7 @@ export default function HomePage() { }, [getFundCodesSignature, scheduleSync]); useEffect(() => { - const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades']); + const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades', 'viewMode']); const onStorage = (e) => { if (!e.key) return; if (!keys.has(e.key)) return; @@ -2442,6 +2466,12 @@ export default function HomePage() { }; }, [getFundCodesSignature, scheduleSync]); + const applyViewMode = useCallback((mode) => { + if (mode !== 'card' && mode !== 'list') return; + setViewMode(mode); + storageHelper.setItem('viewMode', mode); + }, [storageHelper]); + const toggleFavorite = (code) => { setFavorites(prev => { const next = new Set(prev); @@ -2596,6 +2626,10 @@ export default function HomePage() { if (savedHoldings && typeof savedHoldings === 'object') { setHoldings(savedHoldings); } + const savedViewMode = localStorage.getItem('viewMode'); + if (savedViewMode === 'card' || savedViewMode === 'list') { + setViewMode(savedViewMode); + } } catch { } }, []); @@ -2968,7 +3002,7 @@ export default function HomePage() { const toggleViewMode = () => { const nextMode = viewMode === 'card' ? 'list' : 'card'; - setViewMode(nextMode); + applyViewMode(nextMode); }; const requestRemoveFund = (fund) => { @@ -3179,6 +3213,8 @@ export default function HomePage() { }) : []; + const viewMode = payload.viewMode === 'list' ? 'list' : 'card'; + return JSON.stringify({ funds: uniqueFundCodes, favorites, @@ -3186,7 +3222,8 @@ export default function HomePage() { collapsedCodes, refreshMs: Number.isFinite(payload.refreshMs) ? payload.refreshMs : 30000, holdings, - pendingTrades + pendingTrades, + viewMode }); } @@ -3196,6 +3233,7 @@ export default function HomePage() { const favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); const groups = JSON.parse(localStorage.getItem('groups') || '[]'); const collapsedCodes = JSON.parse(localStorage.getItem('collapsedCodes') || '[]'); + const viewMode = localStorage.getItem('viewMode') === 'list' ? 'list' : 'card'; const fundCodes = new Set( Array.isArray(funds) ? funds.map((f) => f?.code).filter(Boolean) @@ -3252,6 +3290,7 @@ export default function HomePage() { refreshMs: parseInt(localStorage.getItem('refreshMs') || '30000', 10), holdings: cleanedHoldings, pendingTrades: cleanedPendingTrades, + viewMode, exportedAt: nowInTz().toISOString() }; } catch { @@ -3263,6 +3302,7 @@ export default function HomePage() { refreshMs: 30000, holdings: {}, pendingTrades: [], + viewMode: 'card', exportedAt: nowInTz().toISOString() }; } @@ -3298,7 +3338,7 @@ export default function HomePage() { storageHelper.setItem('refreshMs', String(nextRefreshMs)); if (cloudData.viewMode === 'card' || cloudData.viewMode === 'list') { - setViewMode(cloudData.viewMode); + applyViewMode(cloudData.viewMode); } const nextHoldings = cloudData.holdings && typeof cloudData.holdings === 'object' ? cloudData.holdings : {}; @@ -3415,6 +3455,7 @@ export default function HomePage() { groups: JSON.parse(localStorage.getItem('groups') || '[]'), collapsedCodes: JSON.parse(localStorage.getItem('collapsedCodes') || '[]'), refreshMs: parseInt(localStorage.getItem('refreshMs') || '30000', 10), + viewMode: localStorage.getItem('viewMode') === 'list' ? 'list' : 'card', holdings: JSON.parse(localStorage.getItem('holdings') || '{}'), pendingTrades: JSON.parse(localStorage.getItem('pendingTrades') || '[]'), exportedAt: nowInTz().toISOString() @@ -3520,7 +3561,7 @@ export default function HomePage() { storageHelper.setItem('refreshMs', String(data.refreshMs)); } if (data.viewMode === 'card' || data.viewMode === 'list') { - setViewMode(data.viewMode); + applyViewMode(data.viewMode); } if (data.holdings && typeof data.holdings === 'object') { @@ -3907,7 +3948,7 @@ export default function HomePage() {
-
+
) : ( <> - +
+ +
{currentTab !== 'all' && currentTab !== 'fav' && (