119 lines
4.4 KiB
React
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>
|
|
);
|
|
}
|