feat:表格列排序
This commit is contained in:
@@ -23,7 +23,25 @@ import {
|
|||||||
} from '@dnd-kit/sortable';
|
} from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import ConfirmModal from './ConfirmModal';
|
import ConfirmModal from './ConfirmModal';
|
||||||
import { DragIcon, ExitIcon, ResetIcon, SettingsIcon, StarIcon, TrashIcon } from './Icons';
|
import PcTableSettingModal from './PcTableSettingModal';
|
||||||
|
import { DragIcon, ExitIcon, SettingsIcon, StarIcon, TrashIcon } from './Icons';
|
||||||
|
|
||||||
|
const NON_FROZEN_COLUMN_IDS = [
|
||||||
|
'navOrEstimate',
|
||||||
|
'yesterdayChangePercent',
|
||||||
|
'estimateChangePercent',
|
||||||
|
'holdingAmount',
|
||||||
|
'todayProfit',
|
||||||
|
'holdingProfit',
|
||||||
|
];
|
||||||
|
const COLUMN_HEADERS = {
|
||||||
|
navOrEstimate: '净值/估值',
|
||||||
|
yesterdayChangePercent: '昨日涨跌幅',
|
||||||
|
estimateChangePercent: '估值涨跌幅',
|
||||||
|
holdingAmount: '持仓金额',
|
||||||
|
todayProfit: '当日收益',
|
||||||
|
holdingProfit: '持有收益',
|
||||||
|
};
|
||||||
|
|
||||||
const SortableRowContext = createContext({
|
const SortableRowContext = createContext({
|
||||||
setActivatorNodeRef: null,
|
setActivatorNodeRef: null,
|
||||||
@@ -168,6 +186,69 @@ export default function PcFundTable({
|
|||||||
} catch { }
|
} catch { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStoredColumnOrder = () => {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
if (!raw) return null;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
const order = parsed?.pcTableColumnOrder;
|
||||||
|
if (!Array.isArray(order) || order.length === 0) return null;
|
||||||
|
const valid = order.filter((id) => NON_FROZEN_COLUMN_IDS.includes(id));
|
||||||
|
const missing = NON_FROZEN_COLUMN_IDS.filter((id) => !valid.includes(id));
|
||||||
|
return [...valid, ...missing];
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistColumnOrder = (nextOrder) => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
const nextSettings =
|
||||||
|
parsed && typeof parsed === 'object'
|
||||||
|
? { ...parsed, pcTableColumnOrder: nextOrder }
|
||||||
|
: { pcTableColumnOrder: nextOrder };
|
||||||
|
window.localStorage.setItem('customSettings', JSON.stringify(nextSettings));
|
||||||
|
} catch { }
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStoredColumnVisibility = () => {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
if (!raw) return null;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
const visibility = parsed?.pcTableColumnVisibility;
|
||||||
|
if (!visibility || typeof visibility !== 'object') return null;
|
||||||
|
const normalized = {};
|
||||||
|
NON_FROZEN_COLUMN_IDS.forEach((id) => {
|
||||||
|
const value = visibility[id];
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
normalized[id] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.keys(normalized).length ? normalized : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const persistColumnVisibility = (nextVisibility) => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
const nextSettings =
|
||||||
|
parsed && typeof parsed === 'object'
|
||||||
|
? { ...parsed, pcTableColumnVisibility: nextVisibility }
|
||||||
|
: { pcTableColumnVisibility: nextVisibility };
|
||||||
|
window.localStorage.setItem('customSettings', JSON.stringify(nextSettings));
|
||||||
|
} catch { }
|
||||||
|
};
|
||||||
|
|
||||||
const [columnSizing, setColumnSizing] = useState(() => {
|
const [columnSizing, setColumnSizing] = useState(() => {
|
||||||
const stored = getStoredColumnSizing();
|
const stored = getStoredColumnSizing();
|
||||||
if (stored.actions) {
|
if (stored.actions) {
|
||||||
@@ -176,12 +257,45 @@ export default function PcFundTable({
|
|||||||
}
|
}
|
||||||
return stored;
|
return stored;
|
||||||
});
|
});
|
||||||
|
const [columnOrder, setColumnOrder] = useState(() => getStoredColumnOrder() ?? [...NON_FROZEN_COLUMN_IDS]);
|
||||||
|
const [columnVisibility, setColumnVisibility] = useState(() => {
|
||||||
|
const stored = getStoredColumnVisibility();
|
||||||
|
if (stored) return stored;
|
||||||
|
const allVisible = {};
|
||||||
|
NON_FROZEN_COLUMN_IDS.forEach((id) => {
|
||||||
|
allVisible[id] = true;
|
||||||
|
});
|
||||||
|
return allVisible;
|
||||||
|
});
|
||||||
|
const [settingModalOpen, setSettingModalOpen] = useState(false);
|
||||||
const [resetConfirmOpen, setResetConfirmOpen] = useState(false);
|
const [resetConfirmOpen, setResetConfirmOpen] = useState(false);
|
||||||
const handleResetSizing = () => {
|
const handleResetSizing = () => {
|
||||||
setColumnSizing({});
|
setColumnSizing({});
|
||||||
persistColumnSizing({});
|
persistColumnSizing({});
|
||||||
setResetConfirmOpen(false);
|
setResetConfirmOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetColumnOrder = () => {
|
||||||
|
const defaultOrder = [...NON_FROZEN_COLUMN_IDS];
|
||||||
|
setColumnOrder(defaultOrder);
|
||||||
|
persistColumnOrder(defaultOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetColumnVisibility = () => {
|
||||||
|
const allVisible = {};
|
||||||
|
NON_FROZEN_COLUMN_IDS.forEach((id) => {
|
||||||
|
allVisible[id] = true;
|
||||||
|
});
|
||||||
|
setColumnVisibility(allVisible);
|
||||||
|
persistColumnVisibility(allVisible);
|
||||||
|
};
|
||||||
|
const handleToggleColumnVisibility = (columnId, visible) => {
|
||||||
|
setColumnVisibility((prev = {}) => {
|
||||||
|
const next = { ...prev, [columnId]: visible };
|
||||||
|
persistColumnVisibility(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
const onRemoveFundRef = useRef(onRemoveFund);
|
const onRemoveFundRef = useRef(onRemoveFund);
|
||||||
const onToggleFavoriteRef = useRef(onToggleFavorite);
|
const onToggleFavoriteRef = useRef(onToggleFavorite);
|
||||||
const onRemoveFromGroupRef = useRef(onRemoveFromGroup);
|
const onRemoveFromGroupRef = useRef(onRemoveFromGroup);
|
||||||
@@ -463,12 +577,12 @@ export default function PcFundTable({
|
|||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation?.();
|
e.stopPropagation?.();
|
||||||
setResetConfirmOpen(true);
|
setSettingModalOpen(true);
|
||||||
}}
|
}}
|
||||||
title="重置列宽"
|
title="个性化设置"
|
||||||
style={{ border: 'none', width: '24px', height: '24px', backgroundColor: 'transparent', color: 'var(--text)' }}
|
style={{ border: 'none', width: '24px', height: '24px', backgroundColor: 'transparent', color: 'var(--text)' }}
|
||||||
>
|
>
|
||||||
<ResetIcon width="14" height="14" />
|
<SettingsIcon width="14" height="14" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -531,6 +645,22 @@ export default function PcFundTable({
|
|||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
columnSizing,
|
columnSizing,
|
||||||
|
columnOrder,
|
||||||
|
columnVisibility,
|
||||||
|
},
|
||||||
|
onColumnOrderChange: (updater) => {
|
||||||
|
setColumnOrder((prev) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(prev) : prev;
|
||||||
|
persistColumnOrder(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onColumnVisibilityChange: (updater) => {
|
||||||
|
setColumnVisibility((prev = {}) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(prev) : (updater || {});
|
||||||
|
persistColumnVisibility(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
initialState: {
|
initialState: {
|
||||||
columnPinning: {
|
columnPinning: {
|
||||||
@@ -752,6 +882,20 @@ export default function PcFundTable({
|
|||||||
confirmText="重置"
|
confirmText="重置"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<PcTableSettingModal
|
||||||
|
open={settingModalOpen}
|
||||||
|
onClose={() => setSettingModalOpen(false)}
|
||||||
|
columns={columnOrder.map((id) => ({ id, header: COLUMN_HEADERS[id] ?? id }))}
|
||||||
|
onColumnReorder={(newOrder) => {
|
||||||
|
setColumnOrder(newOrder);
|
||||||
|
persistColumnOrder(newOrder);
|
||||||
|
}}
|
||||||
|
columnVisibility={columnVisibility}
|
||||||
|
onToggleColumnVisibility={handleToggleColumnVisibility}
|
||||||
|
onResetColumnOrder={handleResetColumnOrder}
|
||||||
|
onResetColumnVisibility={handleResetColumnVisibility}
|
||||||
|
onResetSizing={() => setResetConfirmOpen(true)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { SettingsIcon } from './Icons';
|
import { useEffect, useState } from 'react';
|
||||||
|
import ConfirmModal from './ConfirmModal';
|
||||||
|
import { ResetIcon, SettingsIcon } from './Icons';
|
||||||
|
|
||||||
export default function SettingsModal({
|
export default function SettingsModal({
|
||||||
onClose,
|
onClose,
|
||||||
@@ -10,15 +12,38 @@ export default function SettingsModal({
|
|||||||
exportLocalData,
|
exportLocalData,
|
||||||
importFileRef,
|
importFileRef,
|
||||||
handleImportFileChange,
|
handleImportFileChange,
|
||||||
importMsg
|
importMsg,
|
||||||
|
isMobile,
|
||||||
|
containerWidth = 1200,
|
||||||
|
setContainerWidth,
|
||||||
|
onResetContainerWidth,
|
||||||
}) {
|
}) {
|
||||||
|
const [sliderDragging, setSliderDragging] = useState(false);
|
||||||
|
const [resetWidthConfirmOpen, setResetWidthConfirmOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!sliderDragging) return;
|
||||||
|
const onPointerUp = () => setSliderDragging(false);
|
||||||
|
document.addEventListener('pointerup', onPointerUp);
|
||||||
|
document.addEventListener('pointercancel', onPointerUp);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('pointerup', onPointerUp);
|
||||||
|
document.removeEventListener('pointercancel', onPointerUp);
|
||||||
|
};
|
||||||
|
}, [sliderDragging]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-overlay" role="dialog" aria-modal="true" aria-label="设置" onClick={onClose}>
|
<div
|
||||||
|
className={`modal-overlay ${sliderDragging ? 'modal-overlay-translucent' : ''}`}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="设置"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
<div className="glass card modal" onClick={(e) => e.stopPropagation()}>
|
<div className="glass card modal" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="title" style={{ marginBottom: 12 }}>
|
<div className="title" style={{ marginBottom: 12 }}>
|
||||||
<SettingsIcon width="20" height="20" />
|
<SettingsIcon width="20" height="20" />
|
||||||
<span>设置</span>
|
<span>设置</span>
|
||||||
<span className="muted">配置刷新频率</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group" style={{ marginBottom: 16 }}>
|
<div className="form-group" style={{ marginBottom: 16 }}>
|
||||||
@@ -53,6 +78,52 @@ export default function SettingsModal({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!isMobile && setContainerWidth && (
|
||||||
|
<div className="form-group" style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }}>
|
||||||
|
<div className="muted" style={{ fontSize: '0.8rem' }}>页面宽度</div>
|
||||||
|
{onResetContainerWidth && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button"
|
||||||
|
onClick={() => setResetWidthConfirmOpen(true)}
|
||||||
|
title="重置页面宽度"
|
||||||
|
style={{
|
||||||
|
border: 'none',
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
padding: 0,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'var(--muted)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResetIcon width="14" height="14" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={600}
|
||||||
|
max={2000}
|
||||||
|
step={10}
|
||||||
|
value={Math.min(2000, Math.max(600, Number(containerWidth) || 1200))}
|
||||||
|
onChange={(e) => setContainerWidth(Number(e.target.value))}
|
||||||
|
onPointerDown={() => setSliderDragging(true)}
|
||||||
|
className="page-width-slider"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 6,
|
||||||
|
accentColor: 'var(--primary)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="muted" style={{ fontSize: '0.8rem', minWidth: 48 }}>
|
||||||
|
{Math.min(2000, Math.max(600, Number(containerWidth) || 1200))}px
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div 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={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
@@ -80,6 +151,18 @@ export default function SettingsModal({
|
|||||||
<button className="button" onClick={saveSettings} disabled={tempSeconds < 30}>保存并关闭</button>
|
<button className="button" onClick={saveSettings} disabled={tempSeconds < 30}>保存并关闭</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{resetWidthConfirmOpen && onResetContainerWidth && (
|
||||||
|
<ConfirmModal
|
||||||
|
title="重置页面宽度"
|
||||||
|
message="是否重置页面宽度为默认值 1200px?"
|
||||||
|
onConfirm={() => {
|
||||||
|
onResetContainerWidth();
|
||||||
|
setResetWidthConfirmOpen(false);
|
||||||
|
}}
|
||||||
|
onCancel={() => setResetWidthConfirmOpen(false)}
|
||||||
|
confirmText="重置"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
125
app/globals.css
125
app/globals.css
@@ -92,6 +92,43 @@ body::before {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-width-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(148, 163, 184, 0.4);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-width-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: transform 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-width-slider::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-width-slider::-moz-range-thumb {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.glass {
|
.glass {
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@@ -1582,6 +1619,12 @@ input[type="number"] {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
|
transition: background 0.2s ease, backdrop-filter 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay-translucent {
|
||||||
|
background: rgba(2, 6, 23, 0.15);
|
||||||
|
backdrop-filter: blur(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
@@ -1590,6 +1633,88 @@ input[type="number"] {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PC 表格设置侧弹框 */
|
||||||
|
.pc-table-setting-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(2, 6, 23, 0.5);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-drawer {
|
||||||
|
width: 360px;
|
||||||
|
max-width: 90vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 16px 0 0 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-right: none;
|
||||||
|
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px 20px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-subtitle {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-setting-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-table-column-switch .dca-toggle-track {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .pc-table-setting-drawer .dca-toggle-thumb {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
/* 定投按钮:暗色主题 */
|
/* 定投按钮:暗色主题 */
|
||||||
.dca-btn {
|
.dca-btn {
|
||||||
background: rgba(34, 211, 238, 0.12);
|
background: rgba(34, 211, 238, 0.12);
|
||||||
|
|||||||
37
app/page.jsx
37
app/page.jsx
@@ -311,6 +311,21 @@ export default function HomePage() {
|
|||||||
const [refreshMs, setRefreshMs] = useState(60000);
|
const [refreshMs, setRefreshMs] = useState(60000);
|
||||||
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);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
if (!raw) return;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
const w = parsed?.pcContainerWidth;
|
||||||
|
const num = Number(w);
|
||||||
|
if (Number.isFinite(num)) {
|
||||||
|
setContainerWidth(Math.min(2000, Math.max(600, num)));
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 全局刷新状态
|
// 全局刷新状态
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
@@ -2539,9 +2554,25 @@ export default function HomePage() {
|
|||||||
const ms = Math.max(30, Number(tempSeconds)) * 1000;
|
const ms = Math.max(30, Number(tempSeconds)) * 1000;
|
||||||
setRefreshMs(ms);
|
setRefreshMs(ms);
|
||||||
storageHelper.setItem('refreshMs', String(ms));
|
storageHelper.setItem('refreshMs', String(ms));
|
||||||
|
const w = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
|
||||||
|
setContainerWidth(w);
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
window.localStorage.setItem('customSettings', JSON.stringify({ ...parsed, pcContainerWidth: w }));
|
||||||
|
} catch { }
|
||||||
setSettingsOpen(false);
|
setSettingsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetContainerWidth = () => {
|
||||||
|
setContainerWidth(1200);
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
window.localStorage.setItem('customSettings', JSON.stringify({ ...parsed, pcContainerWidth: 1200 }));
|
||||||
|
} catch { }
|
||||||
|
};
|
||||||
|
|
||||||
const importFileRef = useRef(null);
|
const importFileRef = useRef(null);
|
||||||
const [importMsg, setImportMsg] = useState('');
|
const [importMsg, setImportMsg] = useState('');
|
||||||
|
|
||||||
@@ -3301,7 +3332,7 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container content">
|
<div className="container content" style={{ width: containerWidth }}>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showThemeTransition && (
|
{showThemeTransition && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -4797,6 +4828,10 @@ export default function HomePage() {
|
|||||||
importFileRef={importFileRef}
|
importFileRef={importFileRef}
|
||||||
handleImportFileChange={handleImportFileChange}
|
handleImportFileChange={handleImportFileChange}
|
||||||
importMsg={importMsg}
|
importMsg={importMsg}
|
||||||
|
isMobile={isMobile}
|
||||||
|
containerWidth={containerWidth}
|
||||||
|
setContainerWidth={setContainerWidth}
|
||||||
|
onResetContainerWidth={handleResetContainerWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user