feat: 全局设置新增显示大盘指数开关
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
保存并关闭
|
保存并关闭
|
||||||
|
|||||||
60
app/page.jsx
60
app/page.jsx
@@ -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) : {};
|
||||||
window.localStorage.setItem('customSettings', JSON.stringify({
|
if (targetIsMobile) {
|
||||||
...parsed,
|
// 仅更新当前运行端对应的开关键
|
||||||
pcContainerWidth: w,
|
window.localStorage.setItem('customSettings', JSON.stringify({
|
||||||
showMarketIndex: shouldShowMarketIndex,
|
...parsed,
|
||||||
}));
|
pcContainerWidth: w,
|
||||||
|
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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user