import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { X } from "lucide-react"; import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useNavigate } from "react-router"; import { getActiveBanners, type BannerAnnouncement } from "@/services/api/announcements"; import { BannerAnnouncementLinkHint, BannerAnnouncementTitle, bannerAnnouncementBarStyle, } from "@/components/layout/banner-announcement-content"; import { useUserStore } from "@/stores/use-user-store"; const BANNER_AUTO_SLIDE_MS = 4000; const BANNER_DISMISS_STORAGE_PREFIX = "yingce.banner-announcements.dismissed"; export function BannerAnnouncementsSlider() { const navigate = useNavigate(); const reducedMotion = useReducedMotion(); const userId = useUserStore((state) => state.user?.id); const [currentIndex, setCurrentIndex] = useState(0); const [dismissed, setDismissed] = useState(false); const { data } = useQuery({ queryKey: ["active-banner-announcements"], queryFn: getActiveBanners, staleTime: 60_000, refetchInterval: 3 * 60_000, }); const banners = data?.banners || []; const fingerprint = bannerFeedFingerprint(banners); const hasLink = Boolean(banners[currentIndex % Math.max(banners.length, 1)]?.link); useEffect(() => { if (banners.length <= 1) return; const timer = window.setInterval(() => { setCurrentIndex((prev) => (prev + 1) % banners.length); }, BANNER_AUTO_SLIDE_MS); return () => window.clearInterval(timer); }, [banners.length]); const handleClick = () => { const current = banners[currentIndex % Math.max(banners.length, 1)]; if (!current?.link) return; if (/^https?:\/\//i.test(current.link)) { window.open(current.link, "_blank", "noopener,noreferrer"); return; } navigate(current.link); }; // banners 数量变化时把下标收拢到有效范围,避免残留越界值。 useEffect(() => { if (banners.length > 0 && currentIndex >= banners.length) { setCurrentIndex(0); } }, [banners.length, currentIndex]); // 关闭状态按「用户 + 通知快照」记录:后台新增或重新编辑通知后快照变化,通知条会再次出现。 useEffect(() => { setDismissed(fingerprint ? hasBannerDismissal(userId, fingerprint) : false); }, [fingerprint, userId]); const handleDismiss = () => { rememberBannerDismissal(userId, fingerprint); setDismissed(true); }; if (!banners.length || dismissed) return null; const currentBanner = banners[currentIndex % banners.length]; // 底色随每条通知的类型切换:加过渡让不同风格的条目轮播时平滑换色,与标题动画节奏一致。 return (