Files
rainblogweb/frontend/src/components/Footer.jsx
T

119 lines
4.4 KiB
React

import React from 'react';
import { Link } from 'react-router-dom';
/**
* 页脚文案渲染:footer_copyright / footer_powered / footer_desc 为管理员字段,
* 纯文本(不含 `<`)按原样文本渲染;含 `<` 时按 HTML+CSS 渲染(self-XSS 与音乐嵌入同级)。
* 仅用于这三个字段,不扩大到其他数据。
*/
function HtmlOrText({ value, className }) {
if (typeof value === 'string' && value.includes('<')) {
return <span className={className} dangerouslySetInnerHTML={{ __html: value }} />;
}
return <span className={className}>{value}</span>;
}
/**
* 前台页脚(按 settings.footer_style 渲染 3 种样式,视觉对齐 /tmp/footer-demo.html 的 02/03/04):
* - classic 经典左右:左 Powered + 版权,右 站点名 v{version} 胶囊徽章
* - columns 分栏导航:品牌 + 固定导航栏 + 底部版权条
* - glass 玻璃卡片:磨砂玻璃卡片(backdrop-filter blur + 半透明底 + 细边框)
* 所有样式都保留站点名 + v{version} 元素;footer_desc 为空时回退到 site_description。
*/
export default function Footer({ settings = {}, version = '' }) {
const siteName = settings.site_name || 'RainWeb';
const style = settings.footer_style || 'classic';
const copyright = settings.footer_copyright || '© 2026 Rainnya Blog. All rights reserved.';
const powered = settings.footer_powered || 'Powered by RainnyaWeb';
const desc = settings.footer_desc || settings.site_description || '';
if (style === 'columns') {
// 导航栏目固定不可编辑(后台提示),首页/博客/论坛走 SPA,管理后台整页跳转
const navCols = [
{ title: '导航', links: [
{ to: '/', label: '首页' },
{ to: '/blog.html', label: '博客' },
{ to: '/forum.html', label: '论坛' },
] },
{ title: '其他', links: [
{ to: '/admin', label: '管理后台', external: true },
{ to: '/profile.html', label: '个人中心' },
] },
];
return (
<footer className="footer-columns">
<div className="fc-grid">
<div className="fc-brand">
<div className="brand-row">
<div className="brand-mark">{siteName.charAt(0)}</div>
<div className="brand-name">{siteName}</div>
{version && <span className="brand-version">v{version}</span>}
</div>
{desc && <HtmlOrText value={desc} className="brand-desc" />}
</div>
{navCols.map((col) => (
<nav key={col.title} className="fc-col">
<div className="col-title">{col.title}</div>
<div className="col-links">
{col.links.map((l) =>
l.external ? (
<a key={l.to} href={l.to}>{l.label}</a>
) : (
<Link key={l.to} to={l.to}>{l.label}</Link>
)
)}
</div>
</nav>
))}
</div>
<div className="fc-bottom">
<HtmlOrText value={powered} />
<span className="sep" />
<HtmlOrText value={copyright} />
</div>
</footer>
);
}
if (style === 'glass') {
return (
<div className="footer-glass-wrap">
<span className="blob blob-1"></span>
<span className="blob blob-2"></span>
<span className="blob blob-3"></span>
<footer className="footer-glass">
<div className="fg-left">
<div className="fg-logo">{siteName.charAt(0)}</div>
<div>
<div className="fg-name">{siteName}</div>
<div className="fg-tag">
{desc && <HtmlOrText value={desc} />}
{desc && <span> · </span>}v{version}
</div>
</div>
</div>
<div className="fg-right">
<HtmlOrText value={copyright} className="fg-copyright" />
<HtmlOrText value={powered} className="fg-powered" />
</div>
</footer>
</div>
);
}
// classic(默认):经典左右
return (
<footer className="footer-classic">
<div className="fc-left">
<HtmlOrText value={powered} />
<span className="sep" />
<HtmlOrText value={copyright} />
</div>
<div className="fc-right">
<span>{siteName}</span>
{version && <span className="fc-version">v{version}</span>}
</div>
</footer>
);
}