Files
jiang13-forum/frontend/src/components/SiteFooter.tsx
freefire 52d328ffc3 chore: 工具链升级到最新版本(Vite 8 / React 19 / react-router 8 / TS 7 / Go 1.26 依赖更新)
- 前端:vite ^8.2.2、react/react-dom ^19.2.8、react-router ^8.3.1(移除 react-router-dom)、typescript ^7.0.2、zustand ^5.0.15;适配升级带来的类型变化(react-hook-form Resolver、ProseMirror node.parent、tiptap focus 参数、timer undefined 等)
- 后端:升级 gin/jwt/minio-go/gorm/modernc-sqlite/x-crypto 等直接依赖至最新;gowebp v0.1.1 已是最新版本
- tsconfig 移除 baseUrl,paths 改为相对路径以兼容 TS7
2026-09-09 00:14:12 +08:00

93 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useSiteBranding } from '../hooks/useSiteBranding';
import { useMediaQuery } from '../hooks/useTheme';
import { Link } from 'react-router';
import { useSitePages } from '../hooks/useSitePages';
import { pagePath } from '../utils/permalink';
import { useForumLimits } from '../hooks/useForumLimits';
function FooterSep() {
return <span className="site-footer__sep" aria-hidden>·</span>;
}
/** 站点页脚:版权、友链/展柜入口、单页、备案号(结构与 Go writeSSRFooter 对齐) */
export default function SiteFooter() {
const { branding } = useSiteBranding();
const { footerPages } = useSitePages();
const { limits } = useForumLimits();
const year = new Date().getFullYear();
const icp = branding.icp_beian?.trim() || '';
const icpURL = branding.icp_beian_url?.trim() || 'https://beian.miit.gov.cn/';
const showFriendLinks = limits.footer_show_friend_links !== false;
const showShowcase = !!limits.footer_show_showcase;
const navItems: React.ReactNode[] = [];
if (showFriendLinks) {
navItems.push(
<span key="links" className="site-footer__friend">
<Link to="/links">友情链接</Link>
</span>,
);
}
if (showShowcase) {
navItems.push(
<span key="showcase" className="site-footer__friend">
<Link to="/showcase">开源展柜</Link>
</span>,
);
}
for (const p of footerPages) {
navItems.push(
<span key={p.slug} className="site-footer__friend">
<Link to={pagePath(p.slug, limits)}>{p.title}</Link>
</span>,
);
}
if (icp) {
navItems.push(
<a
key="icp"
href={icpURL}
target="_blank"
rel="noopener noreferrer"
className="site-footer__icp"
>
{icp}
</a>,
);
}
return (
<footer className="site-footer">
<div className="site-footer__inner">
<div className="site-footer__meta">
<span className="site-footer__copy">
© {year} {branding.name}
</span>
{branding.slogan?.trim() && (
<>
<FooterSep />
<span className="site-footer__slogan">{branding.slogan.trim()}</span>
</>
)}
</div>
<nav className="site-footer__nav" aria-label="站点链接">
{navItems.flatMap((node, i) =>
i === 0 ? [node] : [<FooterSep key={`sep-${i}`} />, node],
)}
</nav>
</div>
</footer>
);
}
/**
* 手机端随内容滚动的页脚(放在 .page-wrap / .post-list-scroll 末尾)。
* 桌面端返回 null,由 MainLayout 壳层贴底页脚负责。
*/
export function InFlowSiteFooter() {
const isMobile = useMediaQuery('(max-width: 768px)');
if (!isMobile) return null;
return <SiteFooter />;
}