feat: 帖子收藏与用户关注

- 新增收藏功能:favorite 服务与 handler、FavoriteButton 组件
- 新增关注功能:follow 服务与 handler、FollowButton 组件
- 用户主页适配关注/粉丝与收藏展示
- flash 消息工具、广告与侧边栏组件微调
This commit is contained in:
2026-09-26 03:02:40 +08:00
parent 31f5360c26
commit d7c6b3a961
40 changed files with 1528 additions and 268 deletions

View File

@@ -136,7 +136,17 @@ export default function TipHost() {
const activeEl = useRef<Element | null>(null);
useEffect(() => {
let pendingTimer: number | null = null;
const clearPending = () => {
if (pendingTimer !== null) {
window.clearTimeout(pendingTimer);
pendingTimer = null;
}
};
const hide = () => {
clearPending();
activeEl.current = null;
setTip(null);
};
@@ -160,8 +170,27 @@ export default function TipHost() {
hide();
return;
}
activeEl.current = host;
placeTip(host, text);
// 同一目标已在显示:直接复位位置,不重新计时
if (activeEl.current === host && pendingTimer === null) {
placeTip(host, text);
return;
}
// data-tip-delay="毫秒":停留一会再显示,期间移出即取消
const delay = Number(host.getAttribute("data-tip-delay") || 0);
if (!Number.isFinite(delay) || delay <= 0) {
clearPending();
activeEl.current = host;
placeTip(host, text);
return;
}
clearPending();
pendingTimer = window.setTimeout(() => {
pendingTimer = null;
const focused = host.contains(document.activeElement);
if (!host.matches(":hover") && !focused) return;
activeEl.current = host;
placeTip(host, text);
}, delay);
};
const onOver = (e: Event) => {
@@ -202,6 +231,7 @@ export default function TipHost() {
window.addEventListener("resize", onScrollOrResize);
return () => {
clearPending();
document.removeEventListener("mouseover", onOver);
document.removeEventListener("mouseout", onOut);
document.removeEventListener("focusin", onOver);