From 2b5f998ab384526738395c3cf02ccf88364e7e3d Mon Sep 17 00:00:00 2001 From: hzm <934585316@qq.com> Date: Mon, 9 Feb 2026 10:49:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AF=B9=20funds=20?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/page.jsx | 138 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/app/page.jsx b/app/page.jsx index daf173d..15173cb 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -281,7 +281,7 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ }); const [isAfter3pm, setIsAfter3pm] = useState(nowInTz().hour() >= 15); const [calcShare, setCalcShare] = useState(null); - + const currentPendingTrades = useMemo(() => { return pendingTrades.filter(t => t.fundCode === fund?.code); }, [pendingTrades, fund]); @@ -302,7 +302,7 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ setShowPendingList(false); } }, [showPendingList, currentPendingTrades]); - + const getEstimatePrice = () => fund?.estPricedCoverage > 0.05 ? fund?.estGsz : (typeof fund?.gsz === 'number' ? fund?.gsz : Number(fund?.dwjz)); const [price, setPrice] = useState(getEstimatePrice()); const [loadingPrice, setLoadingPrice] = useState(false); @@ -312,7 +312,7 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ if (date && fund?.code) { setLoadingPrice(true); setActualDate(null); - + let queryDate = date; if (isAfter3pm) { queryDate = toTz(date).add(1, 'day').format('YYYY-MM-DD'); @@ -347,7 +347,7 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ } else { sellFee = parseFloat(feeValue) || 0; } - + const estimatedReturn = sellAmount - sellFee; useEffect(() => { @@ -390,7 +390,7 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ const isValid = isBuy ? (!!amount && !!feeRate && !!date && calcShare !== null) : (!!share && !!date); - + const handleSetShareFraction = (fraction) => { if(availableShare > 0) { setShare((availableShare * fraction).toFixed(2)); @@ -429,12 +429,12 @@ function TradeModal({ type, fund, holding, onClose, onConfirm, pendingTrades = [ {!showPendingList && !showConfirm && currentPendingTrades.length > 0 && ( -
-
- +
{loadingPrice ? ( 正在查询净值数据... @@ -1909,7 +1909,7 @@ export default function HomePage() { const [holdings, setHoldings] = useState({}); // { [code]: { share: number, cost: number } } const [pendingTrades, setPendingTrades] = useState([]); // [{ id, fundCode, share, date, ... }] const [percentModes, setPercentModes] = useState({}); // { [code]: boolean } - + const holdingsRef = useRef(holdings); const pendingTradesRef = useRef(pendingTrades); @@ -2191,11 +2191,11 @@ export default function HomePage() { // 尝试获取智能净值 const result = await fetchSmartFundNetValue(trade.fundCode, queryDate); - + if (result && result.value > 0) { // 成功获取,执行交易 const current = tempHoldings[trade.fundCode] || { share: 0, cost: 0 }; - + let newShare, newCost; if (trade.type === 'buy') { const feeRate = trade.feeRate || 0; @@ -2208,7 +2208,7 @@ export default function HomePage() { newCost = current.cost; if (newShare === 0) newCost = 0; } - + tempHoldings[trade.fundCode] = { share: newShare, cost: newCost }; stateChanged = true; processedIds.add(trade.id); @@ -2218,13 +2218,13 @@ export default function HomePage() { if (stateChanged) { setHoldings(tempHoldings); storageHelper.setItem('holdings', JSON.stringify(tempHoldings)); - + setPendingTrades(prev => { const next = prev.filter(t => !processedIds.has(t.id)); storageHelper.setItem('pendingTrades', JSON.stringify(next)); return next; }); - + showToast(`已处理 ${processedIds.size} 笔待定交易`, 'success'); } }; @@ -2246,11 +2246,11 @@ export default function HomePage() { isAfter3pm: data.isAfter3pm, timestamp: Date.now() }; - + const next = [...pendingTrades, pending]; setPendingTrades(next); storageHelper.setItem('pendingTrades', JSON.stringify(next)); - + setTradeModal({ open: false, fund: null, type: 'buy' }); showToast('净值暂未更新,已加入待处理队列', 'info'); return; @@ -2362,6 +2362,17 @@ export default function HomePage() { userIdRef.current = user?.id || null; }, [user]); + const getFundCodesSignature = useCallback((value) => { + try { + const list = JSON.parse(value || '[]'); + if (!Array.isArray(list)) return ''; + const codes = list.map((item) => item?.code).filter(Boolean); + return Array.from(new Set(codes)).sort().join('|'); + } catch (e) { + return ''; + } + }, []); + const scheduleSync = useCallback(() => { if (!userIdRef.current) return; if (skipSyncRef.current) return; @@ -2377,8 +2388,14 @@ export default function HomePage() { const storageHelper = useMemo(() => { const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades']); - const triggerSync = (key) => { + 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) { window.localStorage.setItem('localUpdatedAt', nowInTz().toISOString()); } @@ -2387,12 +2404,14 @@ export default function HomePage() { }; return { setItem: (key, value) => { + const prevValue = key === 'funds' ? window.localStorage.getItem(key) : null; window.localStorage.setItem(key, value); - triggerSync(key); + triggerSync(key, prevValue, value); }, removeItem: (key) => { + const prevValue = key === 'funds' ? window.localStorage.getItem(key) : null; window.localStorage.removeItem(key); - triggerSync(key); + triggerSync(key, prevValue, null); }, clear: () => { window.localStorage.clear(); @@ -2402,19 +2421,26 @@ export default function HomePage() { scheduleSync(); } }; - }, [scheduleSync]); + }, [getFundCodesSignature, scheduleSync]); useEffect(() => { const keys = new Set(['funds', 'favorites', 'groups', 'collapsedCodes', 'refreshMs', 'holdings', 'pendingTrades']); const onStorage = (e) => { - if (!e.key || keys.has(e.key)) scheduleSync(); + if (!e.key) return; + if (!keys.has(e.key)) return; + if (e.key === 'funds') { + const prevSig = getFundCodesSignature(e.oldValue); + const nextSig = getFundCodesSignature(e.newValue); + if (prevSig === nextSig) return; + } + scheduleSync(); }; window.addEventListener('storage', onStorage); return () => { window.removeEventListener('storage', onStorage); if (syncDebounceRef.current) clearTimeout(syncDebounceRef.current); }; - }, [scheduleSync]); + }, [getFundCodesSignature, scheduleSync]); const toggleFavorite = (code) => { setFavorites(prev => { @@ -3347,11 +3373,11 @@ export default function HomePage() { const { data: upsertData, error: updateError } = await supabase .from('user_configs') .upsert( - { - user_id: userId, - data: payload, + { + user_id: userId, + data: payload, updated_at: now - }, + }, { onConflict: 'user_id' } ) .select(); @@ -3360,7 +3386,7 @@ export default function HomePage() { if (!upsertData || upsertData.length === 0) { throw new Error('同步失败:未写入任何数据,请检查账号状态或重新登录'); } - + storageHelper.setItem('localUpdatedAt', now); if (showTip) { @@ -4938,9 +4964,9 @@ export default function HomePage() { 更新内容如下:

{updateContent && ( -
- -
)} - + {/* 全局轻提示 Toast */} {toast.show && ( @@ -5092,8 +5118,8 @@ export default function HomePage() { left: '50%', zIndex: 9999, padding: '10px 20px', - background: toast.type === 'error' ? 'rgba(239, 68, 68, 0.9)' : - toast.type === 'success' ? 'rgba(34, 197, 94, 0.9)' : + background: toast.type === 'error' ? 'rgba(239, 68, 68, 0.9)' : + toast.type === 'success' ? 'rgba(34, 197, 94, 0.9)' : 'rgba(30, 41, 59, 0.9)', color: '#fff', borderRadius: '8px',