add: 补充 README 并增加 loading 效果

This commit is contained in:
hzm
2026-01-31 23:24:01 +08:00
parent cd04131929
commit 6fb93e5f3c
3 changed files with 107 additions and 36 deletions

View File

@@ -34,13 +34,21 @@ function RefreshIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M4 12a8 8 0 0 1 12.5-6.9" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<path d="M16 5h3v3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<path d="M16 5h3v3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M20 12a8 8 0 0 1-12.5 6.9" stroke="currentColor" strokeWidth="2" />
<path d="M8 19H5v-3" stroke="currentColor" strokeWidth="2" />
</svg>
);
}
function ChevronIcon(props) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function Stat({ label, value, delta }) {
const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : '';
return (
@@ -62,13 +70,30 @@ export default function HomePage() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const timerRef = useRef(null);
const [manualRefreshing, setManualRefreshing] = useState(false);
// 刷新频率状态
const [refreshMs, setRefreshMs] = useState(30000);
const [settingsOpen, setSettingsOpen] = useState(false);
const [tempSeconds, setTempSeconds] = useState(30);
// 全局刷新状态
const [refreshing, setRefreshing] = useState(false);
// 收起/展开状态
const [collapsedCodes, setCollapsedCodes] = useState(new Set());
const toggleCollapse = (code) => {
setCollapsedCodes(prev => {
const next = new Set(prev);
if (next.has(code)) {
next.delete(code);
} else {
next.add(code);
}
return next;
});
};
useEffect(() => {
try {
const saved = JSON.parse(localStorage.getItem('funds') || '[]');
@@ -221,6 +246,8 @@ export default function HomePage() {
};
const refreshAll = async (codes) => {
if (refreshing) return;
setRefreshing(true);
try {
// 改用串行请求,避免全局回调 jsonpgz 并发冲突
const updated = [];
@@ -241,6 +268,8 @@ export default function HomePage() {
}
} catch (e) {
console.error(e);
} finally {
setRefreshing(false);
}
};
@@ -277,15 +306,10 @@ export default function HomePage() {
};
const manualRefresh = async () => {
if (manualRefreshing) return;
if (refreshing) return;
const codes = funds.map((f) => f.code);
if (!codes.length) return;
setManualRefreshing(true);
try {
await refreshAll(codes);
} finally {
setManualRefreshing(false);
}
await refreshAll(codes);
};
const saveSettings = (e) => {
@@ -307,6 +331,7 @@ export default function HomePage() {
return (
<div className="container content">
<div className="navbar glass">
{refreshing && <div className="loading-bar"></div>}
<div className="brand">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="var(--accent)" strokeWidth="2" />
@@ -323,11 +348,11 @@ export default function HomePage() {
className="icon-button"
aria-label="立即刷新"
onClick={manualRefresh}
disabled={manualRefreshing || funds.length === 0}
aria-busy={manualRefreshing}
disabled={refreshing || funds.length === 0}
aria-busy={refreshing}
title="立即刷新"
>
<RefreshIcon className={manualRefreshing ? 'spin' : ''} width="18" height="18" />
<RefreshIcon className={refreshing ? 'spin' : ''} width="18" height="18" />
</button>
<button
className="icon-button"
@@ -394,12 +419,31 @@ export default function HomePage() {
<Stat label="估值净值" value={f.gsz ?? '—'} />
<Stat label="涨跌幅" value={typeof f.gszzl === 'number' ? `${f.gszzl.toFixed(2)}%` : f.gszzl ?? '—'} delta={Number(f.gszzl) || 0} />
</div>
<div style={{ marginBottom: 8 }} className="title">
<span>前10重仓股票</span>
<span className="muted">涨跌幅 / 占比</span>
<div
style={{ marginBottom: 8, cursor: 'pointer', userSelect: 'none' }}
className="title"
onClick={() => toggleCollapse(f.code)}
>
<div className="row" style={{ width: '100%', flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span>前10重仓股票</span>
<ChevronIcon
width="16"
height="16"
className="muted"
style={{
transform: collapsedCodes.has(f.code) ? 'rotate(-90deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease'
}}
/>
</div>
<span className="muted">涨跌幅 / 占比</span>
</div>
</div>
{Array.isArray(f.holdings) && f.holdings.length ? (
<div className="list">
<div className={`list ${collapsedCodes.has(f.code) ? 'collapsed' : ''}`} style={{
display: collapsedCodes.has(f.code) ? 'none' : 'grid'
}}>
{f.holdings.map((h, idx) => (
<div className="item" key={idx}>
<span className="name">{h.name}</span>
@@ -415,7 +459,7 @@ export default function HomePage() {
))}
</div>
) : (
<div className="muted">暂无重仓数据</div>
<div className="muted" style={{ display: collapsedCodes.has(f.code) ? 'none' : 'block' }}>暂无重仓数据</div>
)}
</div>
</div>