Files
jiang13-forum/frontend/src/components/ShowcaseAsideWidget.tsx
freefire 9e134916a4 fix: 软刷新齐套前保留旧画面,避免一点击就卸光
Logo/下拉静默预热后一次覆盖;commit 不再先 reset/loading;顺带会话预取与可配置 Feed 排序标签。
2026-09-01 04:56:10 +08:00

129 lines
4.5 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Globe2 } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { api } from '../api/client';
import type { CommunityShowcaseItem } from '../api/types';
import { getSessionSnapshot, setSessionSnapshot } from '../utils/sessionPageCache';
import { PAGE_FORCE_REFRESH_EVENT } from '../utils/feedCache';
import { PAGE_SOFT_REFRESH_COMMIT_EVENT } from '../utils/softRefresh';
const SHOWCASE_KEY = 'showcase';
/** 右侧栏:开源展柜精简列表 */
export default function ShowcaseAsideWidget() {
const nav = useNavigate();
const cached = getSessionSnapshot<CommunityShowcaseItem[]>(SHOWCASE_KEY);
const [items, setItems] = useState<CommunityShowcaseItem[]>(() => cached ?? []);
const [loading, setLoading] = useState(() => cached === undefined);
useEffect(() => {
let cancelled = false;
const hit = getSessionSnapshot<CommunityShowcaseItem[]>(SHOWCASE_KEY);
if (hit !== undefined) {
setItems(hit);
setLoading(false);
return;
}
setLoading(true);
api.communityShowcase()
.then((r) => {
if (cancelled) return;
const next = Array.isArray(r.items) ? r.items : [];
setSessionSnapshot(SHOWCASE_KEY, next);
setItems(next);
})
.catch(() => {
if (!cancelled) {
setSessionSnapshot(SHOWCASE_KEY, []);
setItems([]);
}
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; };
}, []);
useEffect(() => {
const fetchShowcase = (opts: { showLoading: boolean }) => {
if (opts.showLoading) setLoading(true);
api.communityShowcase()
.then((r) => {
const next = Array.isArray(r.items) ? r.items : [];
setSessionSnapshot(SHOWCASE_KEY, next);
setItems(next);
})
.catch(() => {
if (opts.showLoading) {
setSessionSnapshot(SHOWCASE_KEY, []);
setItems([]);
}
// 软刷新失败:保持旧 UI
})
.finally(() => setLoading(false));
};
const applyHitOrReload = (showLoading: boolean) => {
const hit = getSessionSnapshot<CommunityShowcaseItem[]>(SHOWCASE_KEY);
if (hit !== undefined) {
setItems(hit);
setLoading(false);
return;
}
fetchShowcase({ showLoading });
};
const onForce = () => applyHitOrReload(true);
const onCommit = () => applyHitOrReload(false);
window.addEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.addEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onCommit);
return () => {
window.removeEventListener(PAGE_FORCE_REFRESH_EVENT, onForce);
window.removeEventListener(PAGE_SOFT_REFRESH_COMMIT_EVENT, onCommit);
};
}, []);
return (
<div className="widget-card widget-card--showcase">
<div className="widget-card-head widget-card-head--split">
<span className="widget-card-head-main">
<Globe2 className="widget-card-icon widget-card-icon--showcase" aria-hidden />
<button type="button" className="widget-friend-links-title" onClick={() => nav('/showcase')}>
</button>
</span>
<button type="button" className="widget-friend-links-more" onClick={() => nav('/showcase')}>
</button>
</div>
<div className="widget-card-body widget-card-body--friend-links">
{loading ? (
<div className="widget-empty"></div>
) : items.length === 0 ? (
<div className="widget-empty">
<button type="button" className="widget-friend-links-more" onClick={() => nav('/showcase')}>
</button>
</div>
) : (
<>
<ul className="widget-friend-links-list">
{items.slice(0, 8).map((item) => (
<li key={item.site_url}>
<a href={item.site_url} target="_blank" rel="noopener noreferrer" title={item.site_name || item.site_url}>
{item.site_name || '未命名站点'}
</a>
</li>
))}
</ul>
{items.length > 8 && (
<button type="button" className="widget-friend-links-more" onClick={() => nav('/showcase')}>
{items.length}
</button>
)}
</>
)}
</div>
</div>
);
}