feat:调整表格模式展示内容

This commit is contained in:
hzm
2026-02-05 21:04:52 +08:00
parent bcf5087259
commit d4b8dbb23b
2 changed files with 155 additions and 16 deletions

View File

@@ -419,7 +419,7 @@ input[type="number"] {
.table-row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1.5fr 60px;
grid-template-columns: 2fr 1fr 1fr 1fr 1.2fr 60px;
align-items: center;
gap: 12px;
padding: 12px 24px !important;
@@ -437,7 +437,7 @@ input[type="number"] {
.table-header-row {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1.5fr 60px;
grid-template-columns: 2fr 1fr 1fr 1fr 1.2fr 60px;
gap: 12px;
padding: 16px 24px;
background: rgba(255, 255, 255, 0.05);
@@ -486,30 +486,53 @@ input[type="number"] {
display: none;
}
.table-row {
grid-template-columns: 1fr 80px 80px;
grid-template-columns: 1fr 80px 100px;
grid-template-areas:
"name value change"
"name time action";
"name time profit";
gap: 4px 12px;
padding: 12px !important;
}
.name-cell { grid-area: name; }
.value-cell { grid-area: value; }
.change-cell { grid-area: change; }
.profit-cell { grid-area: profit; }
.time-cell {
grid-area: time;
justify-content: flex-end;
}
.action-cell {
grid-area: action;
justify-content: flex-end;
}
.action-cell { display: none; }
.table-cell.time-cell span {
font-size: 10px !important;
}
}
.stat-compact .up { color: var(--danger); }
@media (max-width: 768px) {
.action-cell .danger {
display: none;
}
}
.swipe-action-bg {
position: absolute;
top: 1px; /* 留出一点缝隙,或者与 border 对齐 */
bottom: 1px;
right: 0;
width: 80px;
background: linear-gradient(180deg, #ef4444, #f87171);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: #2b0b0b;
z-index: 0;
font-size: 12px;
font-weight: 600;
cursor: pointer;
box-shadow: inset 10px 0 20px -10px rgba(0,0,0,0.2); /* 增加一点内阴影,增加层次感 */
}
.stat-compact .down { color: var(--success); }
.filter-bar {

View File

@@ -1571,10 +1571,21 @@ export default function HomePage() {
const [percentModes, setPercentModes] = useState({}); // { [code]: boolean }
const [isTradingDay, setIsTradingDay] = useState(true); // 默认为交易日,通过接口校正
const tabsRef = useRef(null);
const [fundDeleteConfirm, setFundDeleteConfirm] = useState(null); // { code, name }
const today = new Date();
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
if (typeof window !== 'undefined') {
const checkMobile = () => setIsMobile(window.innerWidth <= 640);
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}
}, []);
// 检查交易日状态
const checkTradingDay = () => {
const now = new Date();
@@ -2320,6 +2331,16 @@ export default function HomePage() {
setViewMode(nextMode);
localStorage.setItem('viewMode', nextMode);
};
const requestRemoveFund = (fund) => {
const h = holdings[fund.code];
const hasHolding = h && typeof h.share === 'number' && h.share > 0;
if (hasHolding) {
setFundDeleteConfirm({ code: fund.code, name: fund.name });
} else {
removeFund(fund.code);
}
};
const addFund = async (e) => {
e?.preventDefault?.();
@@ -2588,7 +2609,8 @@ export default function HomePage() {
actionModal.open ||
tradeModal.open ||
!!clearConfirm ||
donateOpen;
donateOpen ||
!!fundDeleteConfirm;
if (isAnyModalOpen) {
document.body.style.overflow = 'hidden';
@@ -2969,6 +2991,16 @@ export default function HomePage() {
className={viewMode === 'card' ? 'grid' : 'table-container glass'}
>
<div className={viewMode === 'card' ? 'grid col-12' : ''} style={viewMode === 'card' ? { gridColumn: 'span 12', gap: 16 } : {}}>
{viewMode === 'list' && (
<div className="table-header-row">
<div className="table-header-cell">基金名称</div>
<div className="table-header-cell text-right">净值/估值</div>
<div className="table-header-cell text-right">涨跌幅</div>
<div className="table-header-cell text-right">更新时间</div>
<div className="table-header-cell text-right">当日盈亏</div>
<div className="table-header-cell text-center">操作</div>
</div>
)}
<AnimatePresence mode="popLayout">
{displayFunds.map((f) => (
<motion.div
@@ -2979,8 +3011,38 @@ export default function HomePage() {
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
style={{ position: 'relative', overflow: 'hidden' }}
>
{viewMode === 'list' && isMobile && (
<div
className="swipe-action-bg"
onClick={() => requestRemoveFund(f)}
>
<TrashIcon width="18" height="18" />
<span>删除</span>
</div>
)}
<motion.div
className={viewMode === 'card' ? 'glass card' : 'table-row'}
drag={viewMode === 'list' && isMobile ? "x" : false}
dragConstraints={{ left: -80, right: 0 }}
dragElastic={0.1}
onDragEnd={(e, { offset, velocity }) => {
if (viewMode === 'list' && isMobile) {
if (offset.x < -40) {
// 可以在这里触发确认或者仅仅是保持打开状态
// 但简单的实现通常是拖动超过阈值后自动回弹或者直接触发如果想模仿某些App直接删除
// 这里的实现是:用户可以拖动露出后面的按钮,点击按钮删除。
// framer motion 的 dragConstraints 会自动处理回弹,如果没设置 dragSnapToOrigin
}
}
}}
style={{
background: viewMode === 'list' ? 'var(--bg)' : undefined,
position: 'relative',
zIndex: 1
}}
>
<div className={viewMode === 'card' ? 'glass card' : 'table-row'}>
{viewMode === 'list' ? (
<>
<div className="table-cell name-cell">
@@ -3008,7 +3070,27 @@ export default function HomePage() {
</button>
)}
<div className="title-text">
<span className="name-text">{f.name}</span>
<span className="name-text" style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
{f.name}
{f.jzrq === todayStr && (
<span
title="今日净值已更新"
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 16,
height: 16,
borderRadius: '50%',
background: 'rgba(34, 197, 94, 0.2)',
color: '#22c55e',
fontSize: '10px'
}}
>
</span>
)}
</span>
<span className="muted code-text">#{f.code}</span>
</div>
</div>
@@ -3027,7 +3109,7 @@ export default function HomePage() {
</div>
<div className="table-cell text-right change-cell">
<span className={f.zzl > 0 ? 'up' : f.zzl < 0 ? 'down' : ''} style={{ fontWeight: 700 }}>
{f.zzl !== undefined ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` : '--'}
{f.zzl !== undefined ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` : ''}
</span>
</div>
</>
@@ -3051,10 +3133,29 @@ export default function HomePage() {
<div className="table-cell text-right time-cell">
<span className="muted" style={{ fontSize: '12px' }}>{f.gztime || f.time || '-'}</span>
</div>
{(() => {
const holding = holdings[f.code];
const profit = getHoldingProfit(f, holding);
const profitValue = profit ? profit.profitToday : null;
const hasProfit = profitValue !== null;
return (
<div className="table-cell text-right profit-cell">
<span
className={hasProfit ? (profitValue > 0 ? 'up' : profitValue < 0 ? 'down' : '') : 'muted'}
style={{ fontWeight: 700 }}
>
{hasProfit
? `${profitValue > 0 ? '+' : profitValue < 0 ? '-' : ''}¥${Math.abs(profitValue).toFixed(2)}`
: ''}
</span>
</div>
);
})()}
<div className="table-cell text-center action-cell" style={{ gap: 4 }}>
<button
className="icon-button danger"
onClick={() => removeFund(f.code)}
onClick={() => requestRemoveFund(f)}
title="删除"
style={{ width: '28px', height: '28px' }}
>
@@ -3125,7 +3226,7 @@ export default function HomePage() {
<div className="row" style={{ gap: 4 }}>
<button
className="icon-button danger"
onClick={() => removeFund(f.code)}
onClick={() => requestRemoveFund(f)}
title="删除"
style={{ width: '28px', height: '28px' }}
>
@@ -3148,7 +3249,7 @@ export default function HomePage() {
return (
<Stat
label="涨跌幅"
value={f.zzl !== undefined ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` : '--'}
value={f.zzl !== undefined ? `${f.zzl > 0 ? '+' : ''}${Number(f.zzl).toFixed(2)}%` : ''}
delta={f.zzl}
/>
);
@@ -3283,7 +3384,7 @@ export default function HomePage() {
</AnimatePresence>
</>
)}
</div>
</motion.div>
</motion.div>
))}
</AnimatePresence>
@@ -3295,6 +3396,21 @@ export default function HomePage() {
</div>
</div>
<AnimatePresence>
{fundDeleteConfirm && (
<ConfirmModal
title="删除确认"
message={`基金 "${fundDeleteConfirm.name}" 存在持仓记录。删除后将移除该基金及其持仓数据,是否继续?`}
confirmText="确定删除"
onConfirm={() => {
removeFund(fundDeleteConfirm.code);
setFundDeleteConfirm(null);
}}
onCancel={() => setFundDeleteConfirm(null)}
/>
)}
</AnimatePresence>
<div className="footer">
<p style={{ marginBottom: 8 }}>数据源实时估值与重仓直连东方财富仅供个人学习及参考使用数据可能存在延迟不作为任何投资建议</p>
<p style={{ marginBottom: 12 }}>估算数据与真实结算数据会有1%左右误差非股票型基金误差较大</p>