feat: 全局设置新增显示大盘指数开关

This commit is contained in:
hzm
2026-03-20 22:17:43 +08:00
parent 9f6d1bb768
commit 270bc3ab08
4 changed files with 90 additions and 30 deletions

View File

@@ -56,12 +56,15 @@ export default function GroupSummary({
groupName, groupName,
getProfit, getProfit,
stickyTop, stickyTop,
isSticky = false,
onToggleSticky,
masked, masked,
onToggleMasked, onToggleMasked,
marketIndexAccordionHeight,
navbarHeight
}) { }) {
const [showPercent, setShowPercent] = useState(true); const [showPercent, setShowPercent] = useState(true);
const [isMasked, setIsMasked] = useState(masked ?? false); const [isMasked, setIsMasked] = useState(masked ?? false);
const [isSticky, setIsSticky] = useState(false);
const rowRef = useRef(null); const rowRef = useRef(null);
const [assetSize, setAssetSize] = useState(24); const [assetSize, setAssetSize] = useState(24);
const [metricSize, setMetricSize] = useState(18); const [metricSize, setMetricSize] = useState(18);
@@ -165,12 +168,22 @@ export default function GroupSummary({
metricSize, metricSize,
]); ]);
const style = useMemo(()=>{
const style = {};
if (isSticky) {
style.top = stickyTop + 14;
}else if(!marketIndexAccordionHeight) {
style.marginTop = navbarHeight;
}
return style;
},[isSticky, stickyTop, marketIndexAccordionHeight, navbarHeight])
if (!summary.hasHolding) return null; if (!summary.hasHolding) return null;
return ( return (
<div <div
className={isSticky ? 'group-summary-sticky' : ''} className={isSticky ? 'group-summary-sticky' : ''}
style={isSticky && stickyTop ? { top: stickyTop } : {}} style={style}
> >
<div <div
className="glass card group-summary-card" className="glass card group-summary-card"
@@ -183,7 +196,9 @@ export default function GroupSummary({
> >
<span <span
className="sticky-toggle-btn" className="sticky-toggle-btn"
onClick={() => setIsSticky(!isSticky)} onClick={() => {
onToggleSticky?.(!isSticky);
}}
style={{ style={{
position: 'absolute', position: 'absolute',
top: 4, top: 4,

View File

@@ -349,9 +349,14 @@ export default function MobileFundTable({
if (!stickySummaryWrapper) return stickyTop; if (!stickySummaryWrapper) return stickyTop;
const wrapperRect = stickySummaryWrapper.getBoundingClientRect(); const wrapperRect = stickySummaryWrapper.getBoundingClientRect();
const isSummaryStuck = wrapperRect.top <= stickyTop + 1; // 用“实际 DOM 的 top”判断 sticky 是否已生效,避免 mobile 下 stickyTop 入参与 GroupSummary 不一致导致的偏移。
const computedTopStr = window.getComputedStyle(stickySummaryWrapper).top;
const computedTop = Number.parseFloat(computedTopStr);
const baseTop = Number.isFinite(computedTop) ? computedTop : stickyTop;
const isSummaryStuck = wrapperRect.top <= baseTop + 1;
return isSummaryStuck ? stickyTop + stickySummaryWrapper.offsetHeight : stickyTop; // header 使用固定定位(top),所以也用视口坐标系下的 wrapperRect.top + 高度,确保不重叠
return isSummaryStuck ? wrapperRect.top + stickySummaryWrapper.offsetHeight : stickyTop;
}; };
const updateVerticalState = () => { const updateVerticalState = () => {

View File

@@ -20,13 +20,14 @@ export default function SettingsModal({
containerWidth = 1200, containerWidth = 1200,
setContainerWidth, setContainerWidth,
onResetContainerWidth, onResetContainerWidth,
showMarketIndex = true, showMarketIndexPc = true,
setShowMarketIndex, showMarketIndexMobile = true,
}) { }) {
const [sliderDragging, setSliderDragging] = useState(false); const [sliderDragging, setSliderDragging] = useState(false);
const [resetWidthConfirmOpen, setResetWidthConfirmOpen] = useState(false); const [resetWidthConfirmOpen, setResetWidthConfirmOpen] = useState(false);
const [localSeconds, setLocalSeconds] = useState(tempSeconds); const [localSeconds, setLocalSeconds] = useState(tempSeconds);
const [localShowMarketIndex, setLocalShowMarketIndex] = useState(showMarketIndex); const [localShowMarketIndexPc, setLocalShowMarketIndexPc] = useState(showMarketIndexPc);
const [localShowMarketIndexMobile, setLocalShowMarketIndexMobile] = useState(showMarketIndexMobile);
const pageWidthTrackRef = useRef(null); const pageWidthTrackRef = useRef(null);
const clampedWidth = Math.min(2000, Math.max(600, Number(containerWidth) || 1200)); const clampedWidth = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
@@ -60,8 +61,12 @@ export default function SettingsModal({
}, [tempSeconds]); }, [tempSeconds]);
useEffect(() => { useEffect(() => {
setLocalShowMarketIndex(showMarketIndex); setLocalShowMarketIndexPc(showMarketIndexPc);
}, [showMarketIndex]); }, [showMarketIndexPc]);
useEffect(() => {
setLocalShowMarketIndexMobile(showMarketIndexMobile);
}, [showMarketIndexMobile]);
return ( return (
<Dialog <Dialog
@@ -170,16 +175,16 @@ export default function SettingsModal({
</div> </div>
)} )}
<div hidden className="form-group" style={{ marginBottom: 16 }}> <div className="form-group" style={{ marginBottom: 16 }}>
<div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>显示大盘指数</div> <div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>显示大盘指数</div>
<div className="row" style={{ justifyContent: 'flex-start', alignItems: 'center' }}> <div className="row" style={{ justifyContent: 'flex-start', alignItems: 'center' }}>
<Switch <Switch
checked={localShowMarketIndex} checked={isMobile ? localShowMarketIndexMobile : localShowMarketIndexPc}
className="ml-2 scale-125" className="ml-2 scale-125"
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
const nextValue = Boolean(checked); const nextValue = Boolean(checked);
setLocalShowMarketIndex(nextValue); if (isMobile) setLocalShowMarketIndexMobile(nextValue);
setShowMarketIndex?.(nextValue); else setLocalShowMarketIndexPc(nextValue);
}} }}
aria-label="显示大盘指数" aria-label="显示大盘指数"
/> />
@@ -212,7 +217,12 @@ export default function SettingsModal({
<div className="row" style={{ justifyContent: 'flex-end', marginTop: 24 }}> <div className="row" style={{ justifyContent: 'flex-end', marginTop: 24 }}>
<button <button
className="button" className="button"
onClick={(e) => saveSettings(e, localSeconds, localShowMarketIndex)} onClick={(e) => saveSettings(
e,
localSeconds,
isMobile ? localShowMarketIndexMobile : localShowMarketIndexPc,
isMobile
)}
disabled={localSeconds < 30} disabled={localSeconds < 30}
> >
保存并关闭 保存并关闭

View File

@@ -136,7 +136,9 @@ export default function HomePage() {
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
const [tempSeconds, setTempSeconds] = useState(60); const [tempSeconds, setTempSeconds] = useState(60);
const [containerWidth, setContainerWidth] = useState(1200); const [containerWidth, setContainerWidth] = useState(1200);
const [showMarketIndex, setShowMarketIndex] = useState(true); const [showMarketIndexPc, setShowMarketIndexPc] = useState(true);
const [showMarketIndexMobile, setShowMarketIndexMobile] = useState(true);
const [isGroupSummarySticky, setIsGroupSummarySticky] = useState(false);
useEffect(() => { useEffect(() => {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
@@ -149,9 +151,8 @@ export default function HomePage() {
if (Number.isFinite(num)) { if (Number.isFinite(num)) {
setContainerWidth(Math.min(2000, Math.max(600, num))); setContainerWidth(Math.min(2000, Math.max(600, num)));
} }
if (typeof parsed?.showMarketIndex === 'boolean') { if (typeof parsed?.showMarketIndexPc === 'boolean') setShowMarketIndexPc(parsed.showMarketIndexPc);
setShowMarketIndex(parsed.showMarketIndex); if (typeof parsed?.showMarketIndexMobile === 'boolean') setShowMarketIndexMobile(parsed.showMarketIndexMobile);
}
} catch { } } catch { }
}, []); }, []);
@@ -417,6 +418,7 @@ export default function HomePage() {
clearTimeout(timer); clearTimeout(timer);
}; };
}, [groups, currentTab]); // groups 或 tab 变化可能导致 filterBar 高度变化 }, [groups, currentTab]); // groups 或 tab 变化可能导致 filterBar 高度变化
const handleMobileSearchClick = (e) => { const handleMobileSearchClick = (e) => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
@@ -469,6 +471,13 @@ export default function HomePage() {
} }
}, []); }, []);
const shouldShowMarketIndex = isMobile ? showMarketIndexMobile : showMarketIndexPc;
// 当关闭大盘指数时,重置它的高度,避免 top/stickyTop 仍沿用旧值
useEffect(() => {
if (!shouldShowMarketIndex) setMarketIndexAccordionHeight(0);
}, [shouldShowMarketIndex]);
// 检查更新 // 检查更新
const [hasUpdate, setHasUpdate] = useState(false); const [hasUpdate, setHasUpdate] = useState(false);
const [latestVersion, setLatestVersion] = useState(''); const [latestVersion, setLatestVersion] = useState('');
@@ -2806,25 +2815,41 @@ export default function HomePage() {
await refreshAll(codes); await refreshAll(codes);
}; };
const saveSettings = (e, secondsOverride, showMarketIndexOverride) => { const saveSettings = (e, secondsOverride, showMarketIndexOverride, isMobileOverride) => {
e?.preventDefault?.(); e?.preventDefault?.();
const seconds = secondsOverride ?? tempSeconds; const seconds = secondsOverride ?? tempSeconds;
const shouldShowMarketIndex = typeof showMarketIndexOverride === 'boolean' ? showMarketIndexOverride : showMarketIndex;
const ms = Math.max(30, Number(seconds)) * 1000; const ms = Math.max(30, Number(seconds)) * 1000;
setTempSeconds(Math.round(ms / 1000)); setTempSeconds(Math.round(ms / 1000));
setRefreshMs(ms); setRefreshMs(ms);
setShowMarketIndex(shouldShowMarketIndex); const nextShowMarketIndex = typeof showMarketIndexOverride === 'boolean'
? showMarketIndexOverride
: isMobileOverride
? showMarketIndexMobile
: showMarketIndexPc;
const targetIsMobile = Boolean(isMobileOverride);
if (targetIsMobile) setShowMarketIndexMobile(nextShowMarketIndex);
else setShowMarketIndexPc(nextShowMarketIndex);
storageHelper.setItem('refreshMs', String(ms)); storageHelper.setItem('refreshMs', String(ms));
const w = Math.min(2000, Math.max(600, Number(containerWidth) || 1200)); const w = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
setContainerWidth(w); setContainerWidth(w);
try { try {
const raw = window.localStorage.getItem('customSettings'); const raw = window.localStorage.getItem('customSettings');
const parsed = raw ? JSON.parse(raw) : {}; const parsed = raw ? JSON.parse(raw) : {};
if (targetIsMobile) {
// 仅更新当前运行端对应的开关键
window.localStorage.setItem('customSettings', JSON.stringify({ window.localStorage.setItem('customSettings', JSON.stringify({
...parsed, ...parsed,
pcContainerWidth: w, pcContainerWidth: w,
showMarketIndex: shouldShowMarketIndex, showMarketIndexMobile: nextShowMarketIndex,
})); }));
} else {
window.localStorage.setItem('customSettings', JSON.stringify({
...parsed,
pcContainerWidth: w,
showMarketIndexPc: nextShowMarketIndex,
}));
}
triggerCustomSettingsSync(); triggerCustomSettingsSync();
} catch { } } catch { }
setSettingsOpen(false); setSettingsOpen(false);
@@ -3982,7 +4007,7 @@ export default function HomePage() {
</div> </div>
</div> </div>
</div> </div>
{showMarketIndex && ( {shouldShowMarketIndex && (
<MarketIndexAccordion <MarketIndexAccordion
navbarHeight={navbarHeight} navbarHeight={navbarHeight}
onHeightChange={setMarketIndexAccordionHeight} onHeightChange={setMarketIndexAccordionHeight}
@@ -4207,8 +4232,12 @@ export default function HomePage() {
groupName={getGroupName()} groupName={getGroupName()}
getProfit={getHoldingProfit} getProfit={getHoldingProfit}
stickyTop={navbarHeight + marketIndexAccordionHeight + filterBarHeight + (isMobile ? -14 : 0)} stickyTop={navbarHeight + marketIndexAccordionHeight + filterBarHeight + (isMobile ? -14 : 0)}
isSticky={isGroupSummarySticky}
onToggleSticky={(next) => setIsGroupSummarySticky(next)}
masked={maskAmounts} masked={maskAmounts}
onToggleMasked={() => setMaskAmounts((v) => !v)} onToggleMasked={() => setMaskAmounts((v) => !v)}
marketIndexAccordionHeight={marketIndexAccordionHeight}
navbarHeight={navbarHeight}
/> />
{currentTab !== 'all' && currentTab !== 'fav' && ( {currentTab !== 'all' && currentTab !== 'fav' && (
@@ -4258,6 +4287,7 @@ export default function HomePage() {
exit={{ opacity: 0, y: -10 }} exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className={viewMode === 'card' ? 'grid' : 'table-container glass'} className={viewMode === 'card' ? 'grid' : 'table-container glass'}
style={{ marginTop: isGroupSummarySticky ? 50 : 0 }}
> >
<div className={viewMode === 'card' ? 'grid col-12' : ''} style={viewMode === 'card' ? { gridColumn: 'span 12', gap: 16 } : {}}> <div className={viewMode === 'card' ? 'grid col-12' : ''} style={viewMode === 'card' ? { gridColumn: 'span 12', gap: 16 } : {}}>
{/* PC 列表:使用 PcFundTable + 右侧冻结操作列 */} {/* PC 列表:使用 PcFundTable + 右侧冻结操作列 */}
@@ -4810,8 +4840,8 @@ export default function HomePage() {
containerWidth={containerWidth} containerWidth={containerWidth}
setContainerWidth={setContainerWidth} setContainerWidth={setContainerWidth}
onResetContainerWidth={handleResetContainerWidth} onResetContainerWidth={handleResetContainerWidth}
showMarketIndex={showMarketIndex} showMarketIndexPc={showMarketIndexPc}
setShowMarketIndex={setShowMarketIndex} showMarketIndexMobile={showMarketIndexMobile}
/> />
)} )}